@storyshelf/core 0.1.0 → 0.1.3

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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -8
  3. package/dist/adapter/auth.mjs.map +1 -1
  4. package/dist/adapter/capture-runner.d.mts +4 -0
  5. package/dist/adapter/git-host/helpers.d.mts +1 -0
  6. package/dist/adapter/git-host/helpers.mjs +2 -1
  7. package/dist/adapter/git-host/helpers.mjs.map +1 -1
  8. package/dist/adapter/storage.d.mts +1 -0
  9. package/dist/adapters/capture-queue.d.mts +2 -0
  10. package/dist/adapters/webhook-events.mjs +8 -0
  11. package/dist/adapters/webhook-events.mjs.map +1 -1
  12. package/dist/capture/adapter.d.mts +11 -1
  13. package/dist/capture/adapter.mjs +19 -1
  14. package/dist/capture/adapter.mjs.map +1 -1
  15. package/dist/capture/orchestrator.d.mts +7 -0
  16. package/dist/capture/orchestrator.mjs +43 -10
  17. package/dist/capture/orchestrator.mjs.map +1 -1
  18. package/dist/capture/pipeline.d.mts +5 -3
  19. package/dist/capture/pipeline.mjs +18 -6
  20. package/dist/capture/pipeline.mjs.map +1 -1
  21. package/dist/capture/queue.d.mts +2 -0
  22. package/dist/capture/queue.mjs +2 -1
  23. package/dist/capture/queue.mjs.map +1 -1
  24. package/dist/capture/status-fanout.mjs +1 -0
  25. package/dist/capture/status-fanout.mjs.map +1 -1
  26. package/dist/capture/storybook.d.mts +1 -0
  27. package/dist/capture/storybook.mjs +12 -3
  28. package/dist/capture/storybook.mjs.map +1 -1
  29. package/dist/config.d.mts +27 -20
  30. package/dist/config.mjs +16 -2
  31. package/dist/config.mjs.map +1 -1
  32. package/dist/ddl.d.mts +2 -1
  33. package/dist/ddl.mjs +3 -0
  34. package/dist/ddl.mjs.map +1 -1
  35. package/dist/diff/engine.d.mts +8 -0
  36. package/dist/diff/engine.mjs +10 -2
  37. package/dist/diff/engine.mjs.map +1 -1
  38. package/dist/index.d.mts +7 -4
  39. package/dist/index.mjs +10 -3
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/logger.d.mts +3 -3
  42. package/dist/logger.mjs.map +1 -1
  43. package/dist/middleware/csrf.mjs +2 -1
  44. package/dist/middleware/csrf.mjs.map +1 -1
  45. package/dist/middleware/rate-limit.mjs +1 -0
  46. package/dist/middleware/rate-limit.mjs.map +1 -1
  47. package/dist/models/baseline.d.mts +1 -0
  48. package/dist/models/baseline.mjs +3 -1
  49. package/dist/models/baseline.mjs.map +1 -1
  50. package/dist/models/build.d.mts +19 -1
  51. package/dist/models/build.mjs +22 -2
  52. package/dist/models/build.mjs.map +1 -1
  53. package/dist/models/comment.d.mts +2 -0
  54. package/dist/models/comment.mjs +3 -1
  55. package/dist/models/comment.mjs.map +1 -1
  56. package/dist/models/label.d.mts +6 -0
  57. package/dist/models/label.mjs +13 -1
  58. package/dist/models/label.mjs.map +1 -1
  59. package/dist/models/member.mjs +2 -1
  60. package/dist/models/member.mjs.map +1 -1
  61. package/dist/models/project.d.mts +1 -0
  62. package/dist/models/project.mjs +3 -2
  63. package/dist/models/project.mjs.map +1 -1
  64. package/dist/models/snapshot.d.mts +1 -0
  65. package/dist/models/snapshot.mjs +2 -1
  66. package/dist/models/snapshot.mjs.map +1 -1
  67. package/dist/models/status-config.mjs +2 -1
  68. package/dist/models/status-config.mjs.map +1 -1
  69. package/dist/models/token.mjs +2 -1
  70. package/dist/models/token.mjs.map +1 -1
  71. package/dist/models/webhook.d.mts +1 -0
  72. package/dist/models/webhook.mjs +2 -1
  73. package/dist/models/webhook.mjs.map +1 -1
  74. package/dist/pages/build-detail.mjs +2 -1
  75. package/dist/pages/build-detail.mjs.map +1 -1
  76. package/dist/pages/build-diff.mjs +1 -0
  77. package/dist/pages/build-diff.mjs.map +1 -1
  78. package/dist/pages/compute-jobs.mjs +2 -0
  79. package/dist/pages/compute-jobs.mjs.map +1 -1
  80. package/dist/pages/label-detail.mjs +227 -0
  81. package/dist/pages/label-detail.mjs.map +1 -0
  82. package/dist/pages/login.mjs +1 -0
  83. package/dist/pages/login.mjs.map +1 -1
  84. package/dist/pages/project-builds.mjs +1 -0
  85. package/dist/pages/project-builds.mjs.map +1 -1
  86. package/dist/pages/project-create.mjs +1 -0
  87. package/dist/pages/project-create.mjs.map +1 -1
  88. package/dist/pages/project-settings.mjs +33 -42
  89. package/dist/pages/project-settings.mjs.map +1 -1
  90. package/dist/pages/projects.mjs +1 -0
  91. package/dist/pages/projects.mjs.map +1 -1
  92. package/dist/pages/root.mjs +1 -0
  93. package/dist/pages/root.mjs.map +1 -1
  94. package/dist/pages/settings-general.mjs +1 -0
  95. package/dist/pages/settings-general.mjs.map +1 -1
  96. package/dist/pages/settings-labels.mjs +1 -0
  97. package/dist/pages/settings-labels.mjs.map +1 -1
  98. package/dist/pages/settings-members.mjs +1 -0
  99. package/dist/pages/settings-members.mjs.map +1 -1
  100. package/dist/pages/settings-status.mjs +1 -0
  101. package/dist/pages/settings-status.mjs.map +1 -1
  102. package/dist/pages/settings-tests.mjs +149 -0
  103. package/dist/pages/settings-tests.mjs.map +1 -0
  104. package/dist/pages/settings-tokens.mjs +1 -0
  105. package/dist/pages/settings-tokens.mjs.map +1 -1
  106. package/dist/pages/settings-webhooks.mjs +1 -0
  107. package/dist/pages/settings-webhooks.mjs.map +1 -1
  108. package/dist/pages/storybook.mjs +59 -0
  109. package/dist/pages/storybook.mjs.map +1 -0
  110. package/dist/retention/purge.d.mts +3 -0
  111. package/dist/retention/purge.mjs +2 -1
  112. package/dist/retention/purge.mjs.map +1 -1
  113. package/dist/routers/admin.mjs +1 -0
  114. package/dist/routers/admin.mjs.map +1 -1
  115. package/dist/routers/assets.mjs +1 -0
  116. package/dist/routers/assets.mjs.map +1 -1
  117. package/dist/routers/auth.mjs +1 -0
  118. package/dist/routers/auth.mjs.map +1 -1
  119. package/dist/routers/builds.handlers.mjs +15 -3
  120. package/dist/routers/builds.handlers.mjs.map +1 -1
  121. package/dist/routers/builds.mjs +5 -2
  122. package/dist/routers/builds.mjs.map +1 -1
  123. package/dist/routers/comments.mjs +1 -0
  124. package/dist/routers/comments.mjs.map +1 -1
  125. package/dist/routers/helpers.mjs +7 -1
  126. package/dist/routers/helpers.mjs.map +1 -1
  127. package/dist/routers/htmx.mjs +2 -0
  128. package/dist/routers/htmx.mjs.map +1 -1
  129. package/dist/routers/labels.mjs +34 -1
  130. package/dist/routers/labels.mjs.map +1 -1
  131. package/dist/routers/media.mjs +1 -0
  132. package/dist/routers/media.mjs.map +1 -1
  133. package/dist/routers/members.mjs +1 -0
  134. package/dist/routers/members.mjs.map +1 -1
  135. package/dist/routers/projects.mjs +1 -0
  136. package/dist/routers/projects.mjs.map +1 -1
  137. package/dist/routers/schemas.mjs +40 -4
  138. package/dist/routers/schemas.mjs.map +1 -1
  139. package/dist/routers/settings.mjs +22 -0
  140. package/dist/routers/settings.mjs.map +1 -1
  141. package/dist/routers/snapshots.mjs +1 -0
  142. package/dist/routers/snapshots.mjs.map +1 -1
  143. package/dist/routers/status-configs.mjs +1 -0
  144. package/dist/routers/status-configs.mjs.map +1 -1
  145. package/dist/routers/storybook.mjs +113 -0
  146. package/dist/routers/storybook.mjs.map +1 -0
  147. package/dist/routers/tokens.mjs +1 -0
  148. package/dist/routers/tokens.mjs.map +1 -1
  149. package/dist/routers/ui.mjs +25 -2
  150. package/dist/routers/ui.mjs.map +1 -1
  151. package/dist/routers/webhooks.mjs +1 -0
  152. package/dist/routers/webhooks.mjs.map +1 -1
  153. package/dist/schema-tables.mjs +166 -0
  154. package/dist/schema-tables.mjs.map +1 -0
  155. package/dist/schema.d.mts +194 -4352
  156. package/dist/schema.mjs +26 -136
  157. package/dist/schema.mjs.map +1 -1
  158. package/dist/store.mjs +2 -0
  159. package/dist/store.mjs.map +1 -1
  160. package/dist/types.d.mts +14 -1
  161. package/dist/types.mjs +8 -0
  162. package/dist/types.mjs.map +1 -1
  163. package/dist/ui/components.mjs +5 -0
  164. package/dist/ui/components.mjs.map +1 -1
  165. package/dist/ui/document.d.mts +1 -0
  166. package/dist/ui/document.mjs +10 -0
  167. package/dist/ui/document.mjs.map +1 -1
  168. package/dist/ui/styles.mjs +1 -0
  169. package/dist/ui/styles.mjs.map +1 -1
  170. package/dist/ui/theme.mjs +2 -0
  171. package/dist/ui/theme.mjs.map +1 -1
  172. package/dist/urls.d.mts +8 -0
  173. package/dist/urls.mjs +7 -0
  174. package/dist/urls.mjs.map +1 -1
  175. package/dist/utils/hash.mjs +3 -0
  176. package/dist/utils/hash.mjs.map +1 -1
  177. package/dist/utils/paths.d.mts +5 -0
  178. package/dist/utils/paths.mjs +5 -0
  179. package/dist/utils/paths.mjs.map +1 -1
  180. package/dist/utils/ulid.d.mts +2 -0
  181. package/dist/utils/ulid.mjs +5 -3
  182. package/dist/utils/ulid.mjs.map +1 -1
  183. package/package.json +76 -67
@@ -0,0 +1,227 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { BuildModel } from "../models/build.mjs";
5
+ import { ProjectModel } from "../models/project.mjs";
6
+ import { LabelModel } from "../models/label.mjs";
7
+ import { getStore } from "../store.mjs";
8
+ import { DocumentLayout } from "../ui/document.mjs";
9
+ import { Badge, statusTone } from "../ui/components.mjs";
10
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
11
+ //#region src/pages/label-detail.tsx
12
+ /** Resolve a label type's `link_template` against a build, or return null. */
13
+ function resolveLabelLink(template, project, build, value) {
14
+ if (!template) return null;
15
+ const repo = project.gitRepository ?? "";
16
+ return template.replaceAll("{value}", value).replaceAll("{repo}", repo).replaceAll("{branch}", build.gitBranch);
17
+ }
18
+ /** Label page: every build bearing `key=value`, latest first (ADR 0013). */
19
+ async function renderLabelDetailPage(slug, key, value) {
20
+ const project = (await new ProjectModel(getStore().db).list()).find((item) => item.slug === slug);
21
+ if (!project) return null;
22
+ const [builds, labelType] = await Promise.all([new BuildModel(getStore().db).list(project.id, {
23
+ labelKey: key,
24
+ labelValue: value
25
+ }), new LabelModel(getStore().db).getType(project.id, key)]);
26
+ return /* @__PURE__ */ jsxs(DocumentLayout, {
27
+ title: `${key}: ${value}`,
28
+ nav: {
29
+ active: "labels",
30
+ projectSlug: project.slug,
31
+ projectName: project.name
32
+ },
33
+ children: [/* @__PURE__ */ jsxs("div", {
34
+ class: "page-header",
35
+ children: [/* @__PURE__ */ jsx("nav", {
36
+ class: "breadcrumbs",
37
+ "aria-label": "Breadcrumb",
38
+ children: /* @__PURE__ */ jsxs("ol", { children: [
39
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
40
+ href: "/projects",
41
+ children: "Projects"
42
+ }) }),
43
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
44
+ href: `/projects/${project.slug}/builds`,
45
+ children: project.name
46
+ }) }),
47
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("span", {
48
+ "aria-current": "page",
49
+ children: [
50
+ labelType ? labelType.name : key,
51
+ ": ",
52
+ value
53
+ ]
54
+ }) })
55
+ ] })
56
+ }), /* @__PURE__ */ jsxs("div", {
57
+ class: "page-header__row",
58
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("h1", {
59
+ class: "page-header__title",
60
+ children: [
61
+ labelType ? labelType.name : key,
62
+ ": ",
63
+ value
64
+ ]
65
+ }), /* @__PURE__ */ jsxs("p", {
66
+ class: "page-header__desc",
67
+ children: [
68
+ /* @__PURE__ */ jsx(Badge, {
69
+ tone: "neutral",
70
+ children: key
71
+ }),
72
+ " Builds bearing this ",
73
+ labelType ? labelType.name.toLowerCase() : "label",
74
+ ", latest first."
75
+ ]
76
+ })] }), /* @__PURE__ */ jsx("div", {
77
+ class: "page-header__actions",
78
+ children: /* @__PURE__ */ jsx("a", {
79
+ class: "btn btn--ghost",
80
+ href: `/projects/${project.slug}/labels`,
81
+ children: "All labels"
82
+ })
83
+ })]
84
+ })]
85
+ }), builds.length === 0 ? /* @__PURE__ */ jsxs("div", {
86
+ class: "empty",
87
+ children: [/* @__PURE__ */ jsx("h2", {
88
+ class: "empty__title",
89
+ children: "No builds"
90
+ }), /* @__PURE__ */ jsxs("p", {
91
+ class: "empty__desc",
92
+ children: [
93
+ "No build currently bears the label ",
94
+ key,
95
+ ": ",
96
+ value,
97
+ "."
98
+ ]
99
+ })]
100
+ }) : /* @__PURE__ */ jsx("div", {
101
+ class: "table-wrap",
102
+ children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
103
+ /* @__PURE__ */ jsx("th", { children: "Build" }),
104
+ /* @__PURE__ */ jsx("th", { children: "Status" }),
105
+ /* @__PURE__ */ jsx("th", { children: "Author" }),
106
+ /* @__PURE__ */ jsx("th", { children: "Created" }),
107
+ /* @__PURE__ */ jsx("th", {})
108
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: builds.map((build) => {
109
+ const link = labelType?.linkTemplate ? resolveLabelLink(labelType.linkTemplate, project, build, value) : null;
110
+ return /* @__PURE__ */ jsxs("tr", { children: [
111
+ /* @__PURE__ */ jsxs("td", { children: [/* @__PURE__ */ jsx("div", {
112
+ style: "font-weight:600;",
113
+ children: build.gitBranch
114
+ }), /* @__PURE__ */ jsxs("div", {
115
+ class: "field__hint",
116
+ style: "font-family: ui-monospace, monospace;",
117
+ children: [
118
+ build.gitSha.slice(0, 7),
119
+ " ",
120
+ build.message ? `· ${build.message.slice(0, 60)}` : ""
121
+ ]
122
+ })] }),
123
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Badge, {
124
+ tone: statusTone(build.status),
125
+ children: build.status
126
+ }) }),
127
+ /* @__PURE__ */ jsxs("td", { children: [/* @__PURE__ */ jsx("div", { children: build.authorName ?? "—" }), /* @__PURE__ */ jsx("div", {
128
+ class: "field__hint",
129
+ children: build.authorEmail ?? ""
130
+ })] }),
131
+ /* @__PURE__ */ jsx("td", {
132
+ class: "field__hint",
133
+ children: new Date(build.createdAt).toLocaleString()
134
+ }),
135
+ /* @__PURE__ */ jsxs("td", {
136
+ style: "white-space:nowrap;",
137
+ children: [/* @__PURE__ */ jsx("a", {
138
+ class: "btn btn--secondary",
139
+ href: `/projects/${project.slug}/builds/${build.id}`,
140
+ children: "View"
141
+ }), link ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", { style: "margin-left:.35rem;" }), /* @__PURE__ */ jsx("a", {
142
+ class: "btn btn--ghost",
143
+ href: link,
144
+ target: "_blank",
145
+ rel: "noreferrer",
146
+ children: "Open link"
147
+ })] }) : null]
148
+ })
149
+ ] }, build.id);
150
+ }) })] })
151
+ })]
152
+ });
153
+ }
154
+ /** Label types overview page (architecture.md `GET /projects/:slug/labels`). */
155
+ async function renderLabelsPage(slug) {
156
+ const project = (await new ProjectModel(getStore().db).list()).find((item) => item.slug === slug);
157
+ if (!project) return null;
158
+ const labelTypes = await new LabelModel(getStore().db).listTypes(project.id);
159
+ return /* @__PURE__ */ jsxs(DocumentLayout, {
160
+ title: `Labels · ${project.name}`,
161
+ nav: {
162
+ active: "labels",
163
+ projectSlug: project.slug,
164
+ projectName: project.name
165
+ },
166
+ children: [/* @__PURE__ */ jsxs("div", {
167
+ class: "page-header",
168
+ children: [/* @__PURE__ */ jsx("nav", {
169
+ class: "breadcrumbs",
170
+ "aria-label": "Breadcrumb",
171
+ children: /* @__PURE__ */ jsxs("ol", { children: [
172
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
173
+ href: "/projects",
174
+ children: "Projects"
175
+ }) }),
176
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
177
+ href: `/projects/${project.slug}/builds`,
178
+ children: project.name
179
+ }) }),
180
+ /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("span", {
181
+ "aria-current": "page",
182
+ children: "Labels"
183
+ }) })
184
+ ] })
185
+ }), /* @__PURE__ */ jsx("div", {
186
+ class: "page-header__row",
187
+ children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
188
+ class: "page-header__title",
189
+ children: "Labels"
190
+ }), /* @__PURE__ */ jsx("p", {
191
+ class: "page-header__desc",
192
+ children: "Typed labels attach values to builds and link out to external systems."
193
+ })] })
194
+ })]
195
+ }), labelTypes.length === 0 ? /* @__PURE__ */ jsxs("div", {
196
+ class: "empty",
197
+ children: [/* @__PURE__ */ jsx("h2", {
198
+ class: "empty__title",
199
+ children: "No label types"
200
+ }), /* @__PURE__ */ jsx("p", {
201
+ class: "empty__desc",
202
+ children: "No label types are configured for this project."
203
+ })]
204
+ }) : /* @__PURE__ */ jsx("div", {
205
+ class: "table-wrap",
206
+ children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
207
+ /* @__PURE__ */ jsx("th", { children: "Key" }),
208
+ /* @__PURE__ */ jsx("th", { children: "Name" }),
209
+ /* @__PURE__ */ jsx("th", { children: "Template" })
210
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: labelTypes.map((labelType) => /* @__PURE__ */ jsxs("tr", { children: [
211
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Badge, {
212
+ tone: "neutral",
213
+ children: labelType.key
214
+ }) }),
215
+ /* @__PURE__ */ jsx("td", { children: labelType.name }),
216
+ /* @__PURE__ */ jsx("td", {
217
+ style: "max-width:32ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;",
218
+ children: labelType.linkTemplate ?? "—"
219
+ })
220
+ ] }, labelType.id)) })] })
221
+ })]
222
+ });
223
+ }
224
+ //#endregion
225
+ export { renderLabelDetailPage, renderLabelsPage, resolveLabelLink };
226
+
227
+ //# sourceMappingURL=label-detail.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-detail.mjs","names":[],"sources":["../../src/pages/label-detail.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\n\nimport { BuildModel } from \"../models/build.ts\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge, statusTone } from \"../ui/components.tsx\";\n\n/** Resolve a label type's `link_template` against a build, or return null. */\nexport function resolveLabelLink(template: string | null, project: { gitRepository: string | null }, build: { gitBranch: string }, value: string): string | null {\n if (!template) {\n return null;\n }\n const repo = project.gitRepository ?? \"\";\n return template.replaceAll(\"{value}\", value).replaceAll(\"{repo}\", repo).replaceAll(\"{branch}\", build.gitBranch);\n}\n\n/** Label page: every build bearing `key=value`, latest first (ADR 0013). */\nexport async function renderLabelDetailPage(slug: string, key: string, value: string): Promise<RenderedContent | null> {\n const projects = await new ProjectModel(getStore().db).list();\n const project = projects.find((item) => item.slug === slug);\n if (!project) {\n return null;\n }\n const [builds, labelType] = await Promise.all([\n new BuildModel(getStore().db).list(project.id, { labelKey: key, labelValue: value }),\n new LabelModel(getStore().db).getType(project.id, key),\n ]);\n\n return (\n <DocumentLayout title={`${key}: ${value}`} nav={{ active: \"labels\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </li>\n <li>\n <span aria-current=\"page\">\n {labelType ? labelType.name : key}: {value}\n </span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">\n {labelType ? labelType.name : key}: {value}\n </h1>\n <p class=\"page-header__desc\">\n <Badge tone=\"neutral\">{key}</Badge> Builds bearing this {labelType ? labelType.name.toLowerCase() : \"label\"}, latest first.\n </p>\n </div>\n <div class=\"page-header__actions\">\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/labels`}>\n All labels\n </a>\n </div>\n </div>\n </div>\n\n {builds.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No builds</h2>\n <p class=\"empty__desc\">No build currently bears the label {key}: {value}.</p>\n </div>\n ) : (\n <div class=\"table-wrap\">\n <table>\n <thead>\n <tr>\n <th>Build</th>\n <th>Status</th>\n <th>Author</th>\n <th>Created</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {builds.map(\n (build): HtmlEscapedString | Promise<HtmlEscapedString> => {\n const link = labelType?.linkTemplate ? resolveLabelLink(labelType.linkTemplate, project, build, value) : null;\n return (\n <tr key={build.id}>\n <td>\n <div style=\"font-weight:600;\">{build.gitBranch}</div>\n <div class=\"field__hint\" style=\"font-family: ui-monospace, monospace;\">\n {build.gitSha.slice(0, 7)} {build.message ? `· ${build.message.slice(0, 60)}` : \"\"}\n </div>\n </td>\n <td>\n <Badge tone={statusTone(build.status)}>{build.status}</Badge>\n </td>\n <td>\n <div>{build.authorName ?? \"—\"}</div>\n <div class=\"field__hint\">{build.authorEmail ?? \"\"}</div>\n </td>\n <td class=\"field__hint\">{new Date(build.createdAt).toLocaleString()}</td>\n <td style=\"white-space:nowrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds/${build.id}`}>\n View\n </a>\n {link ? (\n <>\n <span style=\"margin-left:.35rem;\" />\n <a class=\"btn btn--ghost\" href={link} target=\"_blank\" rel=\"noreferrer\">\n Open link\n </a>\n </>\n ) : null}\n </td>\n </tr>\n );\n },\n )}\n </tbody>\n </table>\n </div>\n )}\n </DocumentLayout>\n );\n}\n\n/** Label types overview page (architecture.md `GET /projects/:slug/labels`). */\nexport async function renderLabelsPage(slug: string): Promise<RenderedContent | null> {\n const projects = await new ProjectModel(getStore().db).list();\n const project = projects.find((item) => item.slug === slug);\n if (!project) {\n return null;\n }\n const labelTypes = await new LabelModel(getStore().db).listTypes(project.id);\n\n return (\n <DocumentLayout title={`Labels · ${project.name}`} nav={{ active: \"labels\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </li>\n <li>\n <span aria-current=\"page\">Labels</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Labels</h1>\n <p class=\"page-header__desc\">Typed labels attach values to builds and link out to external systems.</p>\n </div>\n </div>\n </div>\n\n {labelTypes.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No label types</h2>\n <p class=\"empty__desc\">No label types are configured for this project.</p>\n </div>\n ) : (\n <div class=\"table-wrap\">\n <table>\n <thead>\n <tr>\n <th>Key</th>\n <th>Name</th>\n <th>Template</th>\n </tr>\n </thead>\n <tbody>\n {labelTypes.map(\n (labelType): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <tr key={labelType.id}>\n <td>\n <Badge tone=\"neutral\">{labelType.key}</Badge>\n </td>\n <td>{labelType.name}</td>\n <td style=\"max-width:32ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\">{labelType.linkTemplate ?? \"—\"}</td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n )}\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAUA,SAAgB,iBAAiB,UAAyB,SAA2C,OAA8B,OAA8B;CAC/J,IAAI,CAAC,UACH,OAAO;CAET,MAAM,OAAO,QAAQ,iBAAiB;CACtC,OAAO,SAAS,WAAW,WAAW,KAAK,CAAC,CAAC,WAAW,UAAU,IAAI,CAAC,CAAC,WAAW,YAAY,MAAM,SAAS;AAChH;;AAGA,eAAsB,sBAAsB,MAAc,KAAa,OAAgD;CAErH,MAAM,WAAU,MADO,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAA,CACnC,MAAM,SAAS,KAAK,SAAS,IAAI;CAC1D,IAAI,CAAC,SACH,OAAO;CAET,MAAM,CAAC,QAAQ,aAAa,MAAM,QAAQ,IAAI,CAC5C,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,QAAQ,IAAI;EAAE,UAAU;EAAK,YAAY;CAAM,CAAC,GACnF,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,QAAQ,IAAI,GAAG,CACvD,CAAC;CAED,OACE,qBAAC,gBAAD;EAAgB,OAAO,GAAG,IAAI,IAAI;EAAS,KAAK;GAAE,QAAQ;GAAU,aAAa,QAAQ;GAAM,aAAa,QAAQ;EAAK;EAAzH,UAAA,CACE,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA,CACE,oBAAC,OAAD;IAAK,OAAM;IAAc,cAAW;IAClC,UAAA,qBAAC,MAAD,EAAA,UAAA;KACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;MAAG,MAAK;MAAY,UAAA;KAAW,CAAA,EAC7B,CAAA;KACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;MAAG,MAAM,aAAa,QAAQ,KAAK;MAAW,UAAA,QAAQ;KAAQ,CAAA,EAC5D,CAAA;KACJ,oBAAC,MAAD,EAAA,UACE,qBAAC,QAAD;MAAM,gBAAa;MAAnB,UAAA;OACG,YAAY,UAAU,OAAO;OAAI;OAAG;MACjC;KACJ,CAAA,EAAA,CAAA;IACF,EAAA,CAAA;GACD,CAAA,GACL,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA,CACE,qBAAC,OAAD,EAAA,UAAA,CACE,qBAAC,MAAD;KAAI,OAAM;KAAV,UAAA;MACG,YAAY,UAAU,OAAO;MAAI;MAAG;KACnC;IACJ,CAAA,GAAA,qBAAC,KAAD;KAAG,OAAM;KAAT,UAAA;MACE,oBAAC,OAAD;OAAO,MAAK;OAAW,UAAA;MAAW,CAAA;MAAC;MAAsB,YAAY,UAAU,KAAK,YAAY,IAAI;MAAQ;KAC3G;IACA,CAAA,CAAA,EAAA,CAAA,GACL,oBAAC,OAAD;KAAK,OAAM;KACT,UAAA,oBAAC,KAAD;MAAG,OAAM;MAAiB,MAAM,aAAa,QAAQ,KAAK;MAAU,UAAA;KAEjE,CAAA;IACA,CAAA,CACF;GACF,CAAA,CAAA;EAEJ,CAAA,GAAA,OAAO,WAAW,IACjB,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA,CACE,oBAAC,MAAD;IAAI,OAAM;IAAe,UAAA;GAAa,CAAA,GACtC,qBAAC,KAAD;IAAG,OAAM;IAAT,UAAA;KAAuB;KAAoC;KAAI;KAAG;KAAM;IAAI;GACzE,CAAA,CAAA;EAEL,CAAA,IAAA,oBAAC,OAAD;GAAK,OAAM;GACT,UAAA,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAA,UAAI,QAAS,CAAA;IACb,oBAAC,MAAD,EAAA,UAAI,SAAU,CAAA;IACd,oBAAC,MAAD,EAAA,UAAI,SAAU,CAAA;IACd,oBAAC,MAAD,EAAA,UAAI,UAAW,CAAA;IACf,oBAAC,MAAD,CAAQ,CAAA;GACN,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,OAAO,KACL,UAA0D;IACzD,MAAM,OAAO,WAAW,eAAe,iBAAiB,UAAU,cAAc,SAAS,OAAO,KAAK,IAAI;IACzG,OACE,qBAAC,MAAD,EAAA,UAAA;KACE,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,OAAD;MAAK,OAAM;MAAoB,UAAA,MAAM;KAAe,CAAA,GACpD,qBAAC,OAAD;MAAK,OAAM;MAAc,OAAM;MAA/B,UAAA;OACG,MAAM,OAAO,MAAM,GAAG,CAAC;OAAE;OAAE,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,GAAG,EAAE,MAAM;MAC7E;KACH,CAAA,CAAA,EAAA,CAAA;KACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,OAAD;MAAO,MAAM,WAAW,MAAM,MAAM;MAAI,UAAA,MAAM;KAAc,CAAA,EAC1D,CAAA;KACJ,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,OAAD,EAAA,UAAM,MAAM,cAAc,IAAS,CAAA,GACnC,oBAAC,OAAD;MAAK,OAAM;MAAe,UAAA,MAAM,eAAe;KAAQ,CAAA,CACrD,EAAA,CAAA;KACJ,oBAAC,MAAD;MAAI,OAAM;MAAe,UAAA,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,eAAe;KAAM,CAAA;KACxE,qBAAC,MAAD;MAAI,OAAM;MAAV,UAAA,CACE,oBAAC,KAAD;OAAG,OAAM;OAAqB,MAAM,aAAa,QAAQ,KAAK,UAAU,MAAM;OAAM,UAAA;MAEjF,CAAA,GACF,OACC,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD,EAAM,OAAM,sBAAuB,CAAA,GACnC,oBAAC,KAAD;OAAG,OAAM;OAAiB,MAAM;OAAM,QAAO;OAAS,KAAI;OAAa,UAAA;MAEpE,CAAA,CACH,EAAA,CAAA,IACA,IACF;;IACF,EAAA,GA5BK,MAAM,EA4BX;GAER,CACF,EACK,CAAA,CACF,EAAA,CAAA;EACJ,CAAA,CAEO;;AAEpB;;AAGA,eAAsB,iBAAiB,MAA+C;CAEpF,MAAM,WAAU,MADO,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAA,CACnC,MAAM,SAAS,KAAK,SAAS,IAAI;CAC1D,IAAI,CAAC,SACH,OAAO;CAET,MAAM,aAAa,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,QAAQ,EAAE;CAE3E,OACE,qBAAC,gBAAD;EAAgB,OAAO,YAAY,QAAQ;EAAQ,KAAK;GAAE,QAAQ;GAAU,aAAa,QAAQ;GAAM,aAAa,QAAQ;EAAK;EAAjI,UAAA,CACE,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA,CACE,oBAAC,OAAD;IAAK,OAAM;IAAc,cAAW;IAClC,UAAA,qBAAC,MAAD,EAAA,UAAA;KACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;MAAG,MAAK;MAAY,UAAA;KAAW,CAAA,EAC7B,CAAA;KACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;MAAG,MAAM,aAAa,QAAQ,KAAK;MAAW,UAAA,QAAQ;KAAQ,CAAA,EAC5D,CAAA;KACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,QAAD;MAAM,gBAAa;MAAO,UAAA;KAAY,CAAA,EACpC,CAAA;IACF,EAAA,CAAA;GACD,CAAA,GACL,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;KAAI,OAAM;KAAqB,UAAA;IAAU,CAAA,GACzC,oBAAC,KAAD;KAAG,OAAM;KAAoB,UAAA;IAAyE,CAAA,CACnG,EAAA,CAAA;GACF,CAAA,CACF;EAEJ,CAAA,GAAA,WAAW,WAAW,IACrB,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA,CACE,oBAAC,MAAD;IAAI,OAAM;IAAe,UAAA;GAAkB,CAAA,GAC3C,oBAAC,KAAD;IAAG,OAAM;IAAc,UAAA;GAAkD,CAAA,CACtE;EAEL,CAAA,IAAA,oBAAC,OAAD;GAAK,OAAM;GACT,UAAA,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAA,UAAI,MAAO,CAAA;IACX,oBAAC,MAAD,EAAA,UAAI,OAAQ,CAAA;IACZ,oBAAC,MAAD,EAAA,UAAI,WAAY,CAAA;GACd,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,WAAW,KACT,cACC,qBAAC,MAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAA,UACE,oBAAC,OAAD;KAAO,MAAK;KAAW,UAAA,UAAU;IAAW,CAAA,EAC1C,CAAA;IACJ,oBAAC,MAAD,EAAA,UAAK,UAAU,KAAS,CAAA;IACxB,oBAAC,MAAD;KAAI,OAAM;KAAgF,UAAA,UAAU,gBAAgB;IAAQ,CAAA;GAC1H,EAAA,GANK,UAAU,EAMf,CAER,EACK,CAAA,CACF,EAAA,CAAA;EACJ,CAAA,CAEO;;AAEpB"}
@@ -4,6 +4,7 @@ import.meta.url;
4
4
  import { DocumentLayout } from "../ui/document.mjs";
5
5
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
6
6
  //#region src/pages/login.tsx
7
+ /** Sign-in page with password form and optional SSO button. */
7
8
  function renderLoginPage(state = {}) {
8
9
  return /* @__PURE__ */ jsx(DocumentLayout, {
9
10
  title: "Sign in",
@@ -1 +1 @@
1
- {"version":3,"file":"login.mjs","names":[],"sources":["../../src/pages/login.tsx"],"sourcesContent":["import { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\n\nexport interface LoginPageState {\n ssoUrl?: string;\n error?: string;\n}\n\nexport function renderLoginPage(state: LoginPageState = {}): RenderedContent {\n return (\n <DocumentLayout title=\"Sign in\">\n <div class=\"login\" style=\"max-width: 360px; margin: 3rem auto;\">\n <div class=\"card card--padded\">\n <h1 style=\"margin-top:0;\">Sign in</h1>\n\n {state.error ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n <strong class=\"alert__title\">Could not sign in</strong>\n <div class=\"alert__body\">{state.error}</div>\n </div>\n ) : null}\n\n <form method=\"post\" action=\"/auth/login\" novalidate>\n <div class=\"field\">\n <label class=\"field__label\" for=\"password\">\n Password\n </label>\n <input\n class=\"field__input\"\n id=\"password\"\n name=\"password\"\n type=\"password\"\n required\n autofocus\n autocomplete=\"current-password\"\n />\n </div>\n <button class=\"btn btn--primary\" type=\"submit\" style=\"width:100%; margin-top:1rem;\">\n Sign in\n </button>\n </form>\n\n {state.ssoUrl ? (\n <div style=\"margin-top:1rem; text-align:center;\">\n <span style=\"color: var(--color-text-secondary); font-size:.875rem;\">or</span>\n <a class=\"btn btn--secondary\" href={state.ssoUrl} style=\"display:block; margin-top:.5rem;\">\n Sign in with SSO\n </a>\n </div>\n ) : null}\n </div>\n </div>\n </DocumentLayout>\n );\n}"],"mappings":";;;;;;AAOA,SAAgB,gBAAgB,QAAwB,CAAC,GAAoB;CAC3E,OACE,oBAAC,gBAAD;EAAgB,OAAM;EACpB,UAAA,oBAAC,OAAD;GAAK,OAAM;GAAQ,OAAM;GACvB,UAAA,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA;KACE,oBAAC,MAAD;MAAI,OAAM;MAAgB,UAAA;KAAW,CAAA;KAEpC,MAAM,QACL,qBAAC,OAAD;MAAK,OAAM;MAAsB,MAAK;MAAtC,UAAA,CACE,oBAAC,UAAD;OAAQ,OAAM;OAAe,UAAA;MAAyB,CAAA,GACtD,oBAAC,OAAD;OAAK,OAAM;OAAe,UAAA,MAAM;MAAW,CAAA,CACxC;KACH,CAAA,IAAA;KAEJ,qBAAC,QAAD;MAAM,QAAO;MAAO,QAAO;MAAc,YAAA;MAAzC,UAAA,CACE,qBAAC,OAAD;OAAK,OAAM;OAAX,UAAA,CACE,oBAAC,SAAD;QAAO,OAAM;QAAe,KAAI;QAAW,UAAA;OAEpC,CAAA,GACP,oBAAC,SAAD;QACE,OAAM;QACN,IAAG;QACH,MAAK;QACL,MAAK;QACL,UAAA;QACA,WAAA;QACA,cAAa;OACd,CAAA,CACE;MACL,CAAA,GAAA,oBAAC,UAAD;OAAQ,OAAM;OAAmB,MAAK;OAAS,OAAM;OAA+B,UAAA;MAE5E,CAAA,CACJ;;KAEL,MAAM,SACL,qBAAC,OAAD;MAAK,OAAM;MAAX,UAAA,CACE,oBAAC,QAAD;OAAM,OAAM;OAAyD,UAAA;MAAQ,CAAA,GAC7E,oBAAC,KAAD;OAAG,OAAM;OAAqB,MAAM,MAAM;OAAQ,OAAM;OAAmC,UAAA;MAExF,CAAA,CACA;KACH,CAAA,IAAA;IACD;;EACF,CAAA;CACS,CAAA;AAEpB"}
1
+ {"version":3,"file":"login.mjs","names":[],"sources":["../../src/pages/login.tsx"],"sourcesContent":["import { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\n\n/** Form state for the sign-in page (SSO link and error message). */\nexport interface LoginPageState {\n ssoUrl?: string;\n error?: string;\n}\n\n/** Sign-in page with password form and optional SSO button. */\nexport function renderLoginPage(state: LoginPageState = {}): RenderedContent {\n return (\n <DocumentLayout title=\"Sign in\">\n <div class=\"login\" style=\"max-width: 360px; margin: 3rem auto;\">\n <div class=\"card card--padded\">\n <h1 style=\"margin-top:0;\">Sign in</h1>\n\n {state.error ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n <strong class=\"alert__title\">Could not sign in</strong>\n <div class=\"alert__body\">{state.error}</div>\n </div>\n ) : null}\n\n <form method=\"post\" action=\"/auth/login\" novalidate>\n <div class=\"field\">\n <label class=\"field__label\" for=\"password\">\n Password\n </label>\n <input\n class=\"field__input\"\n id=\"password\"\n name=\"password\"\n type=\"password\"\n required\n autofocus\n autocomplete=\"current-password\"\n />\n </div>\n <button class=\"btn btn--primary\" type=\"submit\" style=\"width:100%; margin-top:1rem;\">\n Sign in\n </button>\n </form>\n\n {state.ssoUrl ? (\n <div style=\"margin-top:1rem; text-align:center;\">\n <span style=\"color: var(--color-text-secondary); font-size:.875rem;\">or</span>\n <a class=\"btn btn--secondary\" href={state.ssoUrl} style=\"display:block; margin-top:.5rem;\">\n Sign in with SSO\n </a>\n </div>\n ) : null}\n </div>\n </div>\n </DocumentLayout>\n );\n}"],"mappings":";;;;;;;AASA,SAAgB,gBAAgB,QAAwB,CAAC,GAAoB;CAC3E,OACE,oBAAC,gBAAD;EAAgB,OAAM;EACpB,UAAA,oBAAC,OAAD;GAAK,OAAM;GAAQ,OAAM;GACvB,UAAA,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA;KACE,oBAAC,MAAD;MAAI,OAAM;MAAgB,UAAA;KAAW,CAAA;KAEpC,MAAM,QACL,qBAAC,OAAD;MAAK,OAAM;MAAsB,MAAK;MAAtC,UAAA,CACE,oBAAC,UAAD;OAAQ,OAAM;OAAe,UAAA;MAAyB,CAAA,GACtD,oBAAC,OAAD;OAAK,OAAM;OAAe,UAAA,MAAM;MAAW,CAAA,CACxC;KACH,CAAA,IAAA;KAEJ,qBAAC,QAAD;MAAM,QAAO;MAAO,QAAO;MAAc,YAAA;MAAzC,UAAA,CACE,qBAAC,OAAD;OAAK,OAAM;OAAX,UAAA,CACE,oBAAC,SAAD;QAAO,OAAM;QAAe,KAAI;QAAW,UAAA;OAEpC,CAAA,GACP,oBAAC,SAAD;QACE,OAAM;QACN,IAAG;QACH,MAAK;QACL,MAAK;QACL,UAAA;QACA,WAAA;QACA,cAAa;OACd,CAAA,CACE;MACL,CAAA,GAAA,oBAAC,UAAD;OAAQ,OAAM;OAAmB,MAAK;OAAS,OAAM;OAA+B,UAAA;MAE5E,CAAA,CACJ;;KAEL,MAAM,SACL,qBAAC,OAAD;MAAK,OAAM;MAAX,UAAA,CACE,oBAAC,QAAD;OAAM,OAAM;OAAyD,UAAA;MAAQ,CAAA,GAC7E,oBAAC,KAAD;OAAG,OAAM;OAAqB,MAAM,MAAM;OAAQ,OAAM;OAAmC,UAAA;MAExF,CAAA,CACA;KACH,CAAA,IAAA;IACD;;EACF,CAAA;CACS,CAAA;AAEpB"}
@@ -8,6 +8,7 @@ import { DocumentLayout } from "../ui/document.mjs";
8
8
  import { Badge, statusTone } from "../ui/components.mjs";
9
9
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
10
10
  //#region src/pages/project-builds.tsx
11
+ /** Project builds page: filterable build history for one project. */
11
12
  async function renderProjectBuildsPage(slug, query = {}) {
12
13
  const project = (await new ProjectModel(getStore().db).list()).find((item) => item.slug === slug);
13
14
  if (!project) return null;
@@ -1 +1 @@
1
- {"version":3,"file":"project-builds.mjs","names":[],"sources":["../../src/pages/project-builds.tsx"],"sourcesContent":["import { BuildModel } from \"../models/build.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge, statusTone } from \"../ui/components.tsx\";\n\nexport async function renderProjectBuildsPage(slug: string, query: { status?: string; branch?: string } = {}): Promise<RenderedContent | null> {\n const projects = await new ProjectModel(getStore().db).list();\n const project = projects.find((item) => item.slug === slug);\n if (!project) {\n return null;\n }\n const builds = await new BuildModel(getStore().db).list(project.id, {\n status: query.status as never,\n branch: query.branch,\n });\n\n return (\n <DocumentLayout title={project.name} nav={{ active: \"builds\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <span aria-current=\"page\">{project.name}</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">{project.name}</h1>\n <p class=\"page-header__desc\">\n <code>{project.slug}</code> {project.gitRepository ? `· ${project.gitRepository}` : \"\"} · <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </p>\n </div>\n <div class=\"page-header__actions\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/settings`}>\n Settings\n </a>\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/builds`}>\n Refresh\n </a>\n </div>\n </div>\n </div>\n\n <div class=\"card card--padded\" style=\"margin-bottom:1rem;\">\n <form method=\"get\" action={`/projects/${project.slug}/builds`} style=\"display:flex; gap:.5rem; flex-wrap:wrap; align-items:end;\">\n <div class=\"field\" style=\"margin:0; min-width:160px;\">\n <label class=\"field__label\" for=\"status\">\n Status\n </label>\n <select class=\"field__input\" id=\"status\" name=\"status\">\n <option value=\"\" selected={!query.status}>\n All\n </option>\n <option value=\"pending\" selected={query.status === \"pending\"}>\n pending\n </option>\n <option value=\"reviewing\" selected={query.status === \"reviewing\"}>\n reviewing\n </option>\n <option value=\"approved\" selected={query.status === \"approved\"}>\n approved\n </option>\n <option value=\"rejected\" selected={query.status === \"rejected\"}>\n rejected\n </option>\n <option value=\"failed\" selected={query.status === \"failed\"}>\n failed\n </option>\n </select>\n </div>\n <div class=\"field\" style=\"margin:0; min-width:180px;\">\n <label class=\"field__label\" for=\"branch\">\n Branch\n </label>\n <input class=\"field__input\" id=\"branch\" name=\"branch\" value={query.branch ?? \"\"} placeholder=\"main\" />\n </div>\n <button class=\"btn btn--secondary\" type=\"submit\">\n Filter\n </button>\n {query.status || query.branch ? (\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/builds`}>\n Clear\n </a>\n ) : null}\n </form>\n </div>\n\n {builds.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No builds</h2>\n <p class=\"empty__desc\">\n {query.status || query.branch ? \"No builds match the current filter.\" : \"Upload your first build with the CLI. Builds appear here once uploaded.\"}\n </p>\n </div>\n ) : (\n <div class=\"table-wrap\">\n <table>\n <thead>\n <tr>\n <th>Branch / SHA</th>\n <th>Status</th>\n <th>Snapshots</th>\n <th>Author</th>\n <th>Created</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {builds.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (build) => (\n <tr key={build.id}>\n <td>\n <div style=\"font-weight:600;\">{build.gitBranch}</div>\n <div class=\"field__hint\" style=\"font-family: ui-monospace, monospace;\">\n {build.gitSha.slice(0, 7)} {build.message ? `· ${build.message.slice(0, 60)}` : \"\"}\n </div>\n </td>\n <td>\n <Badge tone={statusTone(build.status)}>{build.status}</Badge>\n </td>\n <td>\n <span class=\"field__hint\">\n {build.changedCount} changed · {build.approvedCount} approved · {build.snapshotCount} total\n </span>\n </td>\n <td>\n <div>{build.authorName ?? \"—\"}</div>\n <div class=\"field__hint\">{build.authorEmail ?? \"\"}</div>\n </td>\n <td class=\"field__hint\">{new Date(build.createdAt).toLocaleString()}</td>\n <td style=\"white-space:nowrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds/${build.id}`}>\n View\n </a>\n <span style=\"margin-left:.35rem;\" />\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/builds/${build.id}/diff`}>\n Review\n </a>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"project-builds.mjs","names":[],"sources":["../../src/pages/project-builds.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\n\nimport { BuildModel } from \"../models/build.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge, statusTone } from \"../ui/components.tsx\";\n\n/** Project builds page: filterable build history for one project. */\nexport async function renderProjectBuildsPage(slug: string, query: { status?: string; branch?: string } = {}): Promise<RenderedContent | null> {\n const projects = await new ProjectModel(getStore().db).list();\n const project = projects.find((item) => item.slug === slug);\n if (!project) {\n return null;\n }\n const builds = await new BuildModel(getStore().db).list(project.id, {\n status: query.status as never,\n branch: query.branch,\n });\n\n return (\n <DocumentLayout title={project.name} nav={{ active: \"builds\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <span aria-current=\"page\">{project.name}</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">{project.name}</h1>\n <p class=\"page-header__desc\">\n <code>{project.slug}</code> {project.gitRepository ? `· ${project.gitRepository}` : \"\"} · <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </p>\n </div>\n <div class=\"page-header__actions\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/settings`}>\n Settings\n </a>\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/builds`}>\n Refresh\n </a>\n </div>\n </div>\n </div>\n\n <div class=\"card card--padded\" style=\"margin-bottom:1rem;\">\n <form method=\"get\" action={`/projects/${project.slug}/builds`} style=\"display:flex; gap:.5rem; flex-wrap:wrap; align-items:end;\">\n <div class=\"field\" style=\"margin:0; min-width:160px;\">\n <label class=\"field__label\" for=\"status\">\n Status\n </label>\n <select class=\"field__input\" id=\"status\" name=\"status\">\n <option value=\"\" selected={!query.status}>\n All\n </option>\n <option value=\"pending\" selected={query.status === \"pending\"}>\n pending\n </option>\n <option value=\"reviewing\" selected={query.status === \"reviewing\"}>\n reviewing\n </option>\n <option value=\"approved\" selected={query.status === \"approved\"}>\n approved\n </option>\n <option value=\"rejected\" selected={query.status === \"rejected\"}>\n rejected\n </option>\n <option value=\"failed\" selected={query.status === \"failed\"}>\n failed\n </option>\n </select>\n </div>\n <div class=\"field\" style=\"margin:0; min-width:180px;\">\n <label class=\"field__label\" for=\"branch\">\n Branch\n </label>\n <input class=\"field__input\" id=\"branch\" name=\"branch\" value={query.branch ?? \"\"} placeholder=\"main\" />\n </div>\n <button class=\"btn btn--secondary\" type=\"submit\">\n Filter\n </button>\n {query.status || query.branch ? (\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/builds`}>\n Clear\n </a>\n ) : null}\n </form>\n </div>\n\n {builds.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No builds</h2>\n <p class=\"empty__desc\">\n {query.status || query.branch ? \"No builds match the current filter.\" : \"Upload your first build with the CLI. Builds appear here once uploaded.\"}\n </p>\n </div>\n ) : (\n <div class=\"table-wrap\">\n <table>\n <thead>\n <tr>\n <th>Branch / SHA</th>\n <th>Status</th>\n <th>Snapshots</th>\n <th>Author</th>\n <th>Created</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {builds.map(\n (build): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <tr key={build.id}>\n <td>\n <div style=\"font-weight:600;\">{build.gitBranch}</div>\n <div class=\"field__hint\" style=\"font-family: ui-monospace, monospace;\">\n {build.gitSha.slice(0, 7)} {build.message ? `· ${build.message.slice(0, 60)}` : \"\"}\n </div>\n </td>\n <td>\n <Badge tone={statusTone(build.status)}>{build.status}</Badge>\n </td>\n <td>\n <span class=\"field__hint\">\n {build.changedCount} changed · {build.approvedCount} approved · {build.snapshotCount} total\n </span>\n </td>\n <td>\n <div>{build.authorName ?? \"—\"}</div>\n <div class=\"field__hint\">{build.authorEmail ?? \"\"}</div>\n </td>\n <td class=\"field__hint\">{new Date(build.createdAt).toLocaleString()}</td>\n <td style=\"white-space:nowrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds/${build.id}`}>\n View\n </a>\n <span style=\"margin-left:.35rem;\" />\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/builds/${build.id}/diff`}>\n Review\n </a>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ import.meta.url;
4
4
  import { DocumentLayout } from "../ui/document.mjs";
5
5
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
6
6
  //#region src/pages/project-create.tsx
7
+ /** New-project form page (one project corresponds to one Storybook). */
7
8
  function renderProjectCreatePage(state = {}) {
8
9
  const values = state.values ?? {};
9
10
  const errors = state.errors ?? {};
@@ -1 +1 @@
1
- {"version":3,"file":"project-create.mjs","names":[],"sources":["../../src/pages/project-create.tsx"],"sourcesContent":["import { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\n\nexport interface ProjectCreateFormState {\n values?: { name?: string; gitRepository?: string; gitDefaultBranch?: string };\n errors?: Record<string, string>;\n globalError?: string;\n}\n\nexport function renderProjectCreatePage(state: ProjectCreateFormState = {}): RenderedContent {\n const values = state.values ?? {};\n const errors = state.errors ?? {};\n return (\n <DocumentLayout title=\"New project\" nav={{ active: \"projects\" }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <span aria-current=\"page\">New project</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Create project</h1>\n <p class=\"page-header__desc\">A project corresponds to one Storybook. You can have multiple projects per repository.</p>\n </div>\n </div>\n </div>\n\n {state.globalError ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n <strong class=\"alert__title\">Could not create project</strong>\n <div class=\"alert__body\">{state.globalError}</div>\n </div>\n ) : null}\n\n <div class=\"card card--padded\" style=\"max-width: 640px;\">\n <form\n method=\"post\"\n action=\"/projects/new\"\n hx-post=\"/projects/new\"\n hx-target=\"body\"\n hx-swap=\"outerHTML\"\n novalidate\n >\n <div class=\"field\">\n <label class=\"field__label\" for=\"name\">\n Name <span aria-hidden=\"true\">*</span>\n </label>\n <input\n class={`field__input ${errors[\"name\"] ? \"field__input--error\" : \"\"}`}\n id=\"name\"\n name=\"name\"\n type=\"text\"\n required\n autofocus\n value={values.name ?? \"\"}\n placeholder=\"Design System\"\n aria-invalid={errors[\"name\"] ? \"true\" : undefined}\n aria-describedby={errors[\"name\"] ? \"name-error\" : \"name-hint\"}\n autocomplete=\"off\"\n />\n {errors[\"name\"] ? (\n <p class=\"field__error\" id=\"name-error\" role=\"alert\">\n {errors[\"name\"]}\n </p>\n ) : (\n <p class=\"field__hint\" id=\"name-hint\">\n Human readable name. A URL-friendly slug is generated automatically.\n </p>\n )}\n </div>\n\n <div class=\"field\">\n <label class=\"field__label\" for=\"gitRepository\">\n Git repository\n </label>\n <input\n class={`field__input ${errors[\"gitRepository\"] ? \"field__input--error\" : \"\"}`}\n id=\"gitRepository\"\n name=\"gitRepository\"\n type=\"text\"\n value={values.gitRepository ?? \"\"}\n placeholder=\"acme/design-system\"\n aria-invalid={errors[\"gitRepository\"] ? \"true\" : undefined}\n aria-describedby={errors[\"gitRepository\"] ? \"gitRepository-error\" : \"gitRepository-hint\"}\n />\n {errors[\"gitRepository\"] ? (\n <p class=\"field__error\" id=\"gitRepository-error\" role=\"alert\">\n {errors[\"gitRepository\"]}\n </p>\n ) : (\n <p class=\"field__hint\" id=\"gitRepository-hint\">\n Optional owner/repo for status checks and links.\n </p>\n )}\n </div>\n\n <div class=\"field\">\n <label class=\"field__label\" for=\"gitDefaultBranch\">\n Default branch\n </label>\n <input\n class=\"field__input\"\n id=\"gitDefaultBranch\"\n name=\"gitDefaultBranch\"\n type=\"text\"\n value={values.gitDefaultBranch ?? \"main\"}\n placeholder=\"main\"\n />\n <p class=\"field__hint\">Baselines fall back to this branch when no branch-specific baseline exists.</p>\n </div>\n\n <div style=\"display:flex; gap:.5rem; margin-top:1rem;\">\n <button class=\"btn btn--primary\" type=\"submit\">\n Create project\n </button>\n <a class=\"btn btn--secondary\" href=\"/projects\">\n Cancel\n </a>\n </div>\n </form>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"project-create.mjs","names":[],"sources":["../../src/pages/project-create.tsx"],"sourcesContent":["import { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\n\n/** Form state for the project creation page (values and validation errors). */\nexport interface ProjectCreateFormState {\n values?: { name?: string; gitRepository?: string; gitDefaultBranch?: string };\n errors?: Record<string, string>;\n globalError?: string;\n}\n\n/** New-project form page (one project corresponds to one Storybook). */\nexport function renderProjectCreatePage(state: ProjectCreateFormState = {}): RenderedContent {\n const values = state.values ?? {};\n const errors = state.errors ?? {};\n return (\n <DocumentLayout title=\"New project\" nav={{ active: \"projects\" }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <span aria-current=\"page\">New project</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Create project</h1>\n <p class=\"page-header__desc\">A project corresponds to one Storybook. You can have multiple projects per repository.</p>\n </div>\n </div>\n </div>\n\n {state.globalError ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n <strong class=\"alert__title\">Could not create project</strong>\n <div class=\"alert__body\">{state.globalError}</div>\n </div>\n ) : null}\n\n <div class=\"card card--padded\" style=\"max-width: 640px;\">\n <form\n method=\"post\"\n action=\"/projects/new\"\n hx-post=\"/projects/new\"\n hx-target=\"body\"\n hx-swap=\"outerHTML\"\n novalidate\n >\n <div class=\"field\">\n <label class=\"field__label\" for=\"name\">\n Name <span aria-hidden=\"true\">*</span>\n </label>\n <input\n class={`field__input ${errors[\"name\"] ? \"field__input--error\" : \"\"}`}\n id=\"name\"\n name=\"name\"\n type=\"text\"\n required\n autofocus\n value={values.name ?? \"\"}\n placeholder=\"Design System\"\n aria-invalid={errors[\"name\"] ? \"true\" : undefined}\n aria-describedby={errors[\"name\"] ? \"name-error\" : \"name-hint\"}\n autocomplete=\"off\"\n />\n {errors[\"name\"] ? (\n <p class=\"field__error\" id=\"name-error\" role=\"alert\">\n {errors[\"name\"]}\n </p>\n ) : (\n <p class=\"field__hint\" id=\"name-hint\">\n Human readable name. A URL-friendly slug is generated automatically.\n </p>\n )}\n </div>\n\n <div class=\"field\">\n <label class=\"field__label\" for=\"gitRepository\">\n Git repository\n </label>\n <input\n class={`field__input ${errors[\"gitRepository\"] ? \"field__input--error\" : \"\"}`}\n id=\"gitRepository\"\n name=\"gitRepository\"\n type=\"text\"\n value={values.gitRepository ?? \"\"}\n placeholder=\"acme/design-system\"\n aria-invalid={errors[\"gitRepository\"] ? \"true\" : undefined}\n aria-describedby={errors[\"gitRepository\"] ? \"gitRepository-error\" : \"gitRepository-hint\"}\n />\n {errors[\"gitRepository\"] ? (\n <p class=\"field__error\" id=\"gitRepository-error\" role=\"alert\">\n {errors[\"gitRepository\"]}\n </p>\n ) : (\n <p class=\"field__hint\" id=\"gitRepository-hint\">\n Optional owner/repo for status checks and links.\n </p>\n )}\n </div>\n\n <div class=\"field\">\n <label class=\"field__label\" for=\"gitDefaultBranch\">\n Default branch\n </label>\n <input\n class=\"field__input\"\n id=\"gitDefaultBranch\"\n name=\"gitDefaultBranch\"\n type=\"text\"\n value={values.gitDefaultBranch ?? \"main\"}\n placeholder=\"main\"\n />\n <p class=\"field__hint\">Baselines fall back to this branch when no branch-specific baseline exists.</p>\n </div>\n\n <div style=\"display:flex; gap:.5rem; margin-top:1rem;\">\n <button class=\"btn btn--primary\" type=\"submit\">\n Create project\n </button>\n <a class=\"btn btn--secondary\" href=\"/projects\">\n Cancel\n </a>\n </div>\n </form>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
@@ -6,10 +6,35 @@ import { renderSettingsGeneral } from "./settings-general.mjs";
6
6
  import { renderSettingsLabels } from "./settings-labels.mjs";
7
7
  import { renderSettingsMembers } from "./settings-members.mjs";
8
8
  import { renderSettingsStatus } from "./settings-status.mjs";
9
+ import { renderSettingsTests } from "./settings-tests.mjs";
9
10
  import { renderSettingsTokens } from "./settings-tokens.mjs";
10
11
  import { renderSettingsWebhooks } from "./settings-webhooks.mjs";
11
12
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
12
13
  //#region src/pages/project-settings.tsx
14
+ function tabHref(project, tab) {
15
+ return tab === "general" ? `/projects/${project.slug}/settings` : `/projects/${project.slug}/settings/${tab}`;
16
+ }
17
+ function renderTabLink(project, activeTab, tab, label) {
18
+ const active = activeTab === tab;
19
+ return /* @__PURE__ */ jsx("a", {
20
+ class: `tabs__link ${active ? "tabs__link--active" : ""}`,
21
+ href: tabHref(project, tab),
22
+ "aria-current": active ? "page" : void 0,
23
+ children: label
24
+ });
25
+ }
26
+ function renderActiveTab(data, formState) {
27
+ const { project, activeTab } = data;
28
+ if (activeTab === "general") return renderSettingsGeneral(project, formState, data.isAdmin);
29
+ if (activeTab === "tests") return renderSettingsTests(project, data.isAdmin, formState);
30
+ if (activeTab === "labels") return renderSettingsLabels(project, data.labelTypes, data.isAdmin);
31
+ if (activeTab === "tokens") return renderSettingsTokens(project, data.tokens, data.isAdmin);
32
+ if (activeTab === "webhooks") return renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState);
33
+ if (activeTab === "members") return renderSettingsMembers(project, data.members, data.isAdmin);
34
+ if (activeTab === "status") return renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin);
35
+ return null;
36
+ }
37
+ /** Project settings shell: tab navigation plus the active settings tab. */
13
38
  function renderProjectSettingsPage(data, formState) {
14
39
  const { project, activeTab } = data;
15
40
  return /* @__PURE__ */ jsxs(DocumentLayout, {
@@ -58,50 +83,16 @@ function renderProjectSettingsPage(data, formState) {
58
83
  class: "tabs",
59
84
  "aria-label": "Settings sections",
60
85
  children: [
61
- /* @__PURE__ */ jsx("a", {
62
- class: `tabs__link ${activeTab === "general" ? "tabs__link--active" : ""}`,
63
- href: `/projects/${project.slug}/settings`,
64
- "aria-current": activeTab === "general" ? "page" : void 0,
65
- children: "General"
66
- }),
67
- /* @__PURE__ */ jsx("a", {
68
- class: `tabs__link ${activeTab === "labels" ? "tabs__link--active" : ""}`,
69
- href: `/projects/${project.slug}/settings/labels`,
70
- "aria-current": activeTab === "labels" ? "page" : void 0,
71
- children: "Labels"
72
- }),
73
- /* @__PURE__ */ jsx("a", {
74
- class: `tabs__link ${activeTab === "tokens" ? "tabs__link--active" : ""}`,
75
- href: `/projects/${project.slug}/settings/tokens`,
76
- "aria-current": activeTab === "tokens" ? "page" : void 0,
77
- children: "Tokens"
78
- }),
79
- /* @__PURE__ */ jsx("a", {
80
- class: `tabs__link ${activeTab === "webhooks" ? "tabs__link--active" : ""}`,
81
- href: `/projects/${project.slug}/settings/webhooks`,
82
- "aria-current": activeTab === "webhooks" ? "page" : void 0,
83
- children: "Webhooks"
84
- }),
85
- /* @__PURE__ */ jsx("a", {
86
- class: `tabs__link ${activeTab === "members" ? "tabs__link--active" : ""}`,
87
- href: `/projects/${project.slug}/settings/members`,
88
- "aria-current": activeTab === "members" ? "page" : void 0,
89
- children: "Members"
90
- }),
91
- /* @__PURE__ */ jsx("a", {
92
- class: `tabs__link ${activeTab === "status" ? "tabs__link--active" : ""}`,
93
- href: `/projects/${project.slug}/settings/status`,
94
- "aria-current": activeTab === "status" ? "page" : void 0,
95
- children: "Git status"
96
- })
86
+ renderTabLink(project, activeTab, "general", "General"),
87
+ renderTabLink(project, activeTab, "tests", "Tests"),
88
+ renderTabLink(project, activeTab, "labels", "Labels"),
89
+ renderTabLink(project, activeTab, "tokens", "Tokens"),
90
+ renderTabLink(project, activeTab, "webhooks", "Webhooks"),
91
+ renderTabLink(project, activeTab, "members", "Members"),
92
+ renderTabLink(project, activeTab, "status", "Git status")
97
93
  ]
98
94
  }),
99
- activeTab === "general" ? renderSettingsGeneral(project, formState, data.isAdmin) : null,
100
- activeTab === "labels" ? renderSettingsLabels(project, data.labelTypes, data.isAdmin) : null,
101
- activeTab === "tokens" ? renderSettingsTokens(project, data.tokens, data.isAdmin) : null,
102
- activeTab === "webhooks" ? renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState) : null,
103
- activeTab === "members" ? renderSettingsMembers(project, data.members, data.isAdmin) : null,
104
- activeTab === "status" ? renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin) : null
95
+ renderActiveTab(data, formState)
105
96
  ]
106
97
  });
107
98
  }
@@ -1 +1 @@
1
- {"version":3,"file":"project-settings.mjs","names":[],"sources":["../../src/pages/project-settings.tsx"],"sourcesContent":["import type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { LabelType, Project, Token } from \"../schema.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { renderSettingsGeneral } from \"./settings-general.tsx\";\nimport { renderSettingsLabels } from \"./settings-labels.tsx\";\nimport { renderSettingsMembers, type SettingsMember } from \"./settings-members.tsx\";\nimport { renderSettingsStatus, type SettingsStatusConfig } from \"./settings-status.tsx\";\nimport { renderSettingsTokens } from \"./settings-tokens.tsx\";\nimport { renderSettingsWebhooks, type SettingsWebhook } from \"./settings-webhooks.tsx\";\n\nexport type SettingsTab = \"general\" | \"labels\" | \"tokens\" | \"webhooks\" | \"members\" | \"status\";\n\nexport interface ProjectSettingsData {\n project: Project;\n activeTab: SettingsTab;\n labelTypes: LabelType[];\n tokens: Array<Omit<Token, \"hash\">>;\n members: SettingsMember[];\n webhooks: SettingsWebhook[];\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\nexport interface SettingsFormState {\n errors?: Record<string, string>;\n globalError?: string;\n secret?: string;\n}\n\nexport function renderProjectSettingsPage(data: ProjectSettingsData, formState?: SettingsFormState): RenderedContent {\n const { project, activeTab } = data;\n return (\n <DocumentLayout title={`${project.name} · Settings`} nav={{ active: \"settings\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </li>\n <li>\n <span aria-current=\"page\">Settings</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Project settings</h1>\n <p class=\"page-header__desc\">Manage general settings, labels, tokens, webhooks and members for {project.name}.</p>\n </div>\n </div>\n </div>\n\n <nav class=\"tabs\" aria-label=\"Settings sections\">\n <a class={`tabs__link ${activeTab === \"general\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings`} aria-current={activeTab === \"general\" ? \"page\" : undefined}>\n General\n </a>\n <a class={`tabs__link ${activeTab === \"labels\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/labels`} aria-current={activeTab === \"labels\" ? \"page\" : undefined}>\n Labels\n </a>\n <a class={`tabs__link ${activeTab === \"tokens\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/tokens`} aria-current={activeTab === \"tokens\" ? \"page\" : undefined}>\n Tokens\n </a>\n <a class={`tabs__link ${activeTab === \"webhooks\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/webhooks`} aria-current={activeTab === \"webhooks\" ? \"page\" : undefined}>\n Webhooks\n </a>\n <a class={`tabs__link ${activeTab === \"members\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/members`} aria-current={activeTab === \"members\" ? \"page\" : undefined}>\n Members\n </a>\n <a class={`tabs__link ${activeTab === \"status\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/status`} aria-current={activeTab === \"status\" ? \"page\" : undefined}>\n Git status\n </a>\n </nav>\n\n {activeTab === \"general\" ? renderSettingsGeneral(project, formState, data.isAdmin) : null}\n {activeTab === \"labels\" ? renderSettingsLabels(project, data.labelTypes, data.isAdmin) : null}\n {activeTab === \"tokens\" ? renderSettingsTokens(project, data.tokens, data.isAdmin) : null}\n {activeTab === \"webhooks\" ? renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState) : null}\n {activeTab === \"members\" ? renderSettingsMembers(project, data.members, data.isAdmin) : null}\n {activeTab === \"status\" ? renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin) : null}\n </DocumentLayout>\n );\n}"],"mappings":";;;;;;;;;;;;AA8BA,SAAgB,0BAA0B,MAA2B,WAAgD;CACnH,MAAM,EAAE,SAAS,cAAc;CAC/B,OACE,qBAAC,gBAAD;EAAgB,OAAO,GAAG,QAAQ,KAAK;EAAc,KAAK;GAAE,QAAQ;GAAY,aAAa,QAAQ;GAAM,aAAa,QAAQ;EAAK;EAArI,UAAA;GACE,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA,CACE,oBAAC,OAAD;KAAK,OAAM;KAAc,cAAW;KAClC,UAAA,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAK;OAAY,UAAA;MAAW,CAAA,EAC7B,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAM,aAAa,QAAQ,KAAK;OAAW,UAAA,QAAQ;MAAQ,CAAA,EAC5D,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,QAAD;OAAM,gBAAa;OAAO,UAAA;MAAc,CAAA,EACtC,CAAA;KACF,EAAA,CAAA;IACD,CAAA,GACL,oBAAC,OAAD;KAAK,OAAM;KACT,UAAA,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,OAAM;MAAqB,UAAA;KAAoB,CAAA,GACnD,qBAAC,KAAD;MAAG,OAAM;MAAT,UAAA;OAA6B;OAAmE,QAAQ;OAAK;MAAI;KAC9G,CAAA,CAAA,EAAA,CAAA;IACF,CAAA,CACF;;GAEL,qBAAC,OAAD;IAAK,OAAM;IAAO,cAAW;IAA7B,UAAA;KACE,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,YAAY,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAY,gBAAc,cAAc,YAAY,SAAS,KAAA;MAAW,UAAA;KAEpL,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,WAAW,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAmB,gBAAc,cAAc,WAAW,SAAS,KAAA;MAAW,UAAA;KAEzL,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,WAAW,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAmB,gBAAc,cAAc,WAAW,SAAS,KAAA;MAAW,UAAA;KAEzL,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,aAAa,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAqB,gBAAc,cAAc,aAAa,SAAS,KAAA;MAAW,UAAA;KAE/L,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,YAAY,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAoB,gBAAc,cAAc,YAAY,SAAS,KAAA;MAAW,UAAA;KAE5L,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,WAAW,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAmB,gBAAc,cAAc,WAAW,SAAS,KAAA;MAAW,UAAA;KAEzL,CAAA;IACA;;GAEJ,cAAc,YAAY,sBAAsB,SAAS,WAAW,KAAK,OAAO,IAAI;GACpF,cAAc,WAAW,qBAAqB,SAAS,KAAK,YAAY,KAAK,OAAO,IAAI;GACxF,cAAc,WAAW,qBAAqB,SAAS,KAAK,QAAQ,KAAK,OAAO,IAAI;GACpF,cAAc,aAAa,uBAAuB,SAAS,KAAK,UAAU,KAAK,SAAS,SAAS,IAAI;GACrG,cAAc,YAAY,sBAAsB,SAAS,KAAK,SAAS,KAAK,OAAO,IAAI;GACvF,cAAc,WAAW,qBAAqB,SAAS,KAAK,eAAe,KAAK,UAAU,KAAK,OAAO,IAAI;EAC7F;;AAEpB"}
1
+ {"version":3,"file":"project-settings.mjs","names":[],"sources":["../../src/pages/project-settings.tsx"],"sourcesContent":["import type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { LabelType, Project, Token } from \"../schema.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { renderSettingsGeneral } from \"./settings-general.tsx\";\nimport { renderSettingsLabels } from \"./settings-labels.tsx\";\nimport { renderSettingsMembers, type SettingsMember } from \"./settings-members.tsx\";\nimport { renderSettingsStatus, type SettingsStatusConfig } from \"./settings-status.tsx\";\nimport { renderSettingsTests } from \"./settings-tests.tsx\";\nimport { renderSettingsTokens } from \"./settings-tokens.tsx\";\nimport { renderSettingsWebhooks, type SettingsWebhook } from \"./settings-webhooks.tsx\";\n\n/** Tabs available in the project settings section. */\nexport type SettingsTab = \"general\" | \"tests\" | \"labels\" | \"tokens\" | \"webhooks\" | \"members\" | \"status\";\n\n/** Data required to render the project settings page with its active tab. */\nexport interface ProjectSettingsData {\n project: Project;\n activeTab: SettingsTab;\n labelTypes: LabelType[];\n tokens: Omit<Token, \"hash\">[];\n members: SettingsMember[];\n webhooks: SettingsWebhook[];\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\n/** Form state shared by the settings tabs (field errors, global error, one-time secret). */\nexport interface SettingsFormState {\n errors?: Record<string, string>;\n globalError?: string;\n secret?: string;\n}\n\nfunction tabHref(project: Project, tab: SettingsTab): string {\n return tab === \"general\" ? `/projects/${project.slug}/settings` : `/projects/${project.slug}/settings/${tab}`;\n}\n\nfunction renderTabLink(project: Project, activeTab: SettingsTab, tab: SettingsTab, label: string): unknown {\n const active = activeTab === tab;\n return (\n <a class={`tabs__link ${active ? \"tabs__link--active\" : \"\"}`} href={tabHref(project, tab)} aria-current={active ? \"page\" : undefined}>\n {label}\n </a>\n );\n}\n\nfunction renderActiveTab(data: ProjectSettingsData, formState?: SettingsFormState): unknown {\n const { project, activeTab } = data;\n if (activeTab === \"general\") return renderSettingsGeneral(project, formState, data.isAdmin);\n if (activeTab === \"tests\") return renderSettingsTests(project, data.isAdmin, formState);\n if (activeTab === \"labels\") return renderSettingsLabels(project, data.labelTypes, data.isAdmin);\n if (activeTab === \"tokens\") return renderSettingsTokens(project, data.tokens, data.isAdmin);\n if (activeTab === \"webhooks\") return renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState);\n if (activeTab === \"members\") return renderSettingsMembers(project, data.members, data.isAdmin);\n if (activeTab === \"status\") return renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin);\n return null;\n}\n\n/** Project settings shell: tab navigation plus the active settings tab. */\nexport function renderProjectSettingsPage(data: ProjectSettingsData, formState?: SettingsFormState): RenderedContent {\n const { project, activeTab } = data;\n return (\n <DocumentLayout title={`${project.name} · Settings`} nav={{ active: \"settings\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </li>\n <li>\n <span aria-current=\"page\">Settings</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Project settings</h1>\n <p class=\"page-header__desc\">Manage general settings, labels, tokens, webhooks and members for {project.name}.</p>\n </div>\n </div>\n </div>\n\n <nav class=\"tabs\" aria-label=\"Settings sections\">\n {renderTabLink(project, activeTab, \"general\", \"General\")}\n {renderTabLink(project, activeTab, \"tests\", \"Tests\")}\n {renderTabLink(project, activeTab, \"labels\", \"Labels\")}\n {renderTabLink(project, activeTab, \"tokens\", \"Tokens\")}\n {renderTabLink(project, activeTab, \"webhooks\", \"Webhooks\")}\n {renderTabLink(project, activeTab, \"members\", \"Members\")}\n {renderTabLink(project, activeTab, \"status\", \"Git status\")}\n </nav>\n\n {renderActiveTab(data, formState)}\n </DocumentLayout>\n );\n}"],"mappings":";;;;;;;;;;;;;AAkCA,SAAS,QAAQ,SAAkB,KAA0B;CAC3D,OAAO,QAAQ,YAAY,aAAa,QAAQ,KAAK,aAAa,aAAa,QAAQ,KAAK,YAAY;AAC1G;AAEA,SAAS,cAAc,SAAkB,WAAwB,KAAkB,OAAwB;CACzG,MAAM,SAAS,cAAc;CAC7B,OACE,oBAAC,KAAD;EAAG,OAAO,cAAc,SAAS,uBAAuB;EAAM,MAAM,QAAQ,SAAS,GAAG;EAAG,gBAAc,SAAS,SAAS,KAAA;EACxH,UAAA;CACA,CAAA;AAEP;AAEA,SAAS,gBAAgB,MAA2B,WAAwC;CAC1F,MAAM,EAAE,SAAS,cAAc;CAC/B,IAAI,cAAc,WAAW,OAAO,sBAAsB,SAAS,WAAW,KAAK,OAAO;CAC1F,IAAI,cAAc,SAAS,OAAO,oBAAoB,SAAS,KAAK,SAAS,SAAS;CACtF,IAAI,cAAc,UAAU,OAAO,qBAAqB,SAAS,KAAK,YAAY,KAAK,OAAO;CAC9F,IAAI,cAAc,UAAU,OAAO,qBAAqB,SAAS,KAAK,QAAQ,KAAK,OAAO;CAC1F,IAAI,cAAc,YAAY,OAAO,uBAAuB,SAAS,KAAK,UAAU,KAAK,SAAS,SAAS;CAC3G,IAAI,cAAc,WAAW,OAAO,sBAAsB,SAAS,KAAK,SAAS,KAAK,OAAO;CAC7F,IAAI,cAAc,UAAU,OAAO,qBAAqB,SAAS,KAAK,eAAe,KAAK,UAAU,KAAK,OAAO;CAChH,OAAO;AACT;;AAGA,SAAgB,0BAA0B,MAA2B,WAAgD;CACnH,MAAM,EAAE,SAAS,cAAc;CAC/B,OACE,qBAAC,gBAAD;EAAgB,OAAO,GAAG,QAAQ,KAAK;EAAc,KAAK;GAAE,QAAQ;GAAY,aAAa,QAAQ;GAAM,aAAa,QAAQ;EAAK;EAArI,UAAA;GACE,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA,CACE,oBAAC,OAAD;KAAK,OAAM;KAAc,cAAW;KAClC,UAAA,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAK;OAAY,UAAA;MAAW,CAAA,EAC7B,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAM,aAAa,QAAQ,KAAK;OAAW,UAAA,QAAQ;MAAQ,CAAA,EAC5D,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,QAAD;OAAM,gBAAa;OAAO,UAAA;MAAc,CAAA,EACtC,CAAA;KACF,EAAA,CAAA;IACD,CAAA,GACL,oBAAC,OAAD;KAAK,OAAM;KACT,UAAA,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,OAAM;MAAqB,UAAA;KAAoB,CAAA,GACnD,qBAAC,KAAD;MAAG,OAAM;MAAT,UAAA;OAA6B;OAAmE,QAAQ;OAAK;MAAI;KAC9G,CAAA,CAAA,EAAA,CAAA;IACF,CAAA,CACF;;GAEL,qBAAC,OAAD;IAAK,OAAM;IAAO,cAAW;IAA7B,UAAA;KACG,cAAc,SAAS,WAAW,WAAW,SAAS;KACtD,cAAc,SAAS,WAAW,SAAS,OAAO;KAClD,cAAc,SAAS,WAAW,UAAU,QAAQ;KACpD,cAAc,SAAS,WAAW,UAAU,QAAQ;KACpD,cAAc,SAAS,WAAW,YAAY,UAAU;KACxD,cAAc,SAAS,WAAW,WAAW,SAAS;KACtD,cAAc,SAAS,WAAW,UAAU,YAAY;IACtD;;GAEJ,gBAAgB,MAAM,SAAS;EAClB;;AAEpB"}
@@ -8,6 +8,7 @@ import { DocumentLayout } from "../ui/document.mjs";
8
8
  import { Badge } from "../ui/components.mjs";
9
9
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
10
10
  //#region src/pages/projects.tsx
11
+ /** Projects overview page: project cards with latest build plus next steps. */
11
12
  async function renderProjectsPage() {
12
13
  const { db, user } = getStore();
13
14
  const projects = await new ProjectModel(db).list();
@@ -1 +1 @@
1
- {"version":3,"file":"projects.mjs","names":[],"sources":["../../src/pages/projects.tsx"],"sourcesContent":["import { ProjectModel } from \"../models/project.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport async function renderProjectsPage(): Promise<RenderedContent> {\n const { db, user } = getStore();\n const projects = await new ProjectModel(db).list();\n const canCreate = !user || user.role === \"admin\" || user.role === \"member\";\n\n const recentCounts = await Promise.all(\n projects.map(async (project) => {\n const builds = await new BuildModel(db).list(project.id);\n return { slug: project.slug, count: builds.length, latest: builds[0] ?? null };\n }),\n );\n const countsBySlug = new Map(recentCounts.map((entry) => [entry.slug, entry]));\n\n return (\n <DocumentLayout title=\"Projects\" nav={{ active: \"projects\" }}>\n <div class=\"page-header\">\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Projects</h1>\n <p class=\"page-header__desc\">Each project is one Storybook. Create a project, then upload builds from CI.</p>\n </div>\n <div class=\"page-header__actions\">\n {canCreate ? (\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n New project\n </a>\n ) : null}\n </div>\n </div>\n </div>\n\n {projects.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No projects yet</h2>\n <p class=\"empty__desc\">Create your first project to start visual testing. Projects are free and unlimited.</p>\n {canCreate ? (\n <div class=\"empty__action\">\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n Create project\n </a>\n </div>\n ) : null}\n </div>\n ) : (\n <div class=\"grid\" style=\"gap:.75rem;\">\n {projects.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (project) => {\n const info = countsBySlug.get(project.slug);\n return (\n <div key={project.id} class=\"card card--padded\">\n <div style=\"display:flex; justify-content:space-between; gap:1rem; flex-wrap:wrap; align-items:flex-start;\">\n <div style=\"min-width:0;\">\n <h2 style=\"margin:0; font-size:1.15rem;\">\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </h2>\n <p class=\"field__hint\" style=\"margin:.2rem 0 0;\">\n <code>{project.slug}</code> {project.gitRepository ? `· ${project.gitRepository}` : \"\"} · default <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </p>\n {info?.latest ? (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n Latest: {info.latest.gitBranch} · {info.latest.gitSha.slice(0, 7)} · <Badge tone={info.latest.status === \"approved\" ? \"success\" : info.latest.status === \"reviewing\" ? \"warning\" : \"neutral\"}>{info.latest.status}</Badge>\n </p>\n ) : (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n No builds yet.\n </p>\n )}\n </div>\n <div style=\"display:flex; gap:.5rem; flex-wrap:wrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds`}>\n Builds {info?.count ? `(${info.count})` : \"\"}\n </a>\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/settings`}>\n Settings\n </a>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n <div class=\"card card--padded\" style=\"margin-top:1rem;\">\n <h3 style=\"margin:0 0 .4rem;\">Next steps</h3>\n <ol style=\"margin:.4rem 0 0; padding-left:1.2rem; color:var(--text-secondary);\">\n <li>\n Create a project (or run <code>npx @storyshelf/cli create --url http://localhost:3000 --name \"My Storybook\" --token $STORYSHELF_ADMIN_TOKEN</code>, or <code>init --url --slug</code> to write <code>.storybook/storyshelf.json</code>)\n </li>\n <li>\n Generate a token in <strong>Settings → Tokens</strong> and set <code>STORYSHELF_TOKEN</code> in CI.\n </li>\n <li>\n Upload: <code>npx @storyshelf/cli upload</code> (or <code>npx @storyshelf/cli</code> defaults to upload when config exists)\n </li>\n </ol>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"projects.mjs","names":[],"sources":["../../src/pages/projects.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge } from \"../ui/components.tsx\";\n\n/** Projects overview page: project cards with latest build plus next steps. */\nexport async function renderProjectsPage(): Promise<RenderedContent> {\n const { db, user } = getStore();\n const projects = await new ProjectModel(db).list();\n const canCreate = !user || user.role === \"admin\" || user.role === \"member\";\n\n const recentCounts = await Promise.all(\n projects.map(async (project) => {\n const builds = await new BuildModel(db).list(project.id);\n return { slug: project.slug, count: builds.length, latest: builds[0] ?? null };\n }),\n );\n const countsBySlug = new Map(recentCounts.map((entry) => [entry.slug, entry]));\n\n return (\n <DocumentLayout title=\"Projects\" nav={{ active: \"projects\" }}>\n <div class=\"page-header\">\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Projects</h1>\n <p class=\"page-header__desc\">Each project is one Storybook. Create a project, then upload builds from CI.</p>\n </div>\n <div class=\"page-header__actions\">\n {canCreate ? (\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n New project\n </a>\n ) : null}\n </div>\n </div>\n </div>\n\n {projects.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No projects yet</h2>\n <p class=\"empty__desc\">Create your first project to start visual testing. Projects are free and unlimited.</p>\n {canCreate ? (\n <div class=\"empty__action\">\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n Create project\n </a>\n </div>\n ) : null}\n </div>\n ) : (\n <div class=\"grid\" style=\"gap:.75rem;\">\n {projects.map(\n (project): HtmlEscapedString | Promise<HtmlEscapedString> => {\n const info = countsBySlug.get(project.slug);\n return (\n <div key={project.id} class=\"card card--padded\">\n <div style=\"display:flex; justify-content:space-between; gap:1rem; flex-wrap:wrap; align-items:flex-start;\">\n <div style=\"min-width:0;\">\n <h2 style=\"margin:0; font-size:1.15rem;\">\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </h2>\n <p class=\"field__hint\" style=\"margin:.2rem 0 0;\">\n <code>{project.slug}</code> {project.gitRepository ? `· ${project.gitRepository}` : \"\"} · default <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </p>\n {info?.latest ? (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n Latest: {info.latest.gitBranch} · {info.latest.gitSha.slice(0, 7)} · <Badge tone={info.latest.status === \"approved\" ? \"success\" : (info.latest.status === \"reviewing\" ? \"warning\" : \"neutral\")}>{info.latest.status}</Badge>\n </p>\n ) : (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n No builds yet.\n </p>\n )}\n </div>\n <div style=\"display:flex; gap:.5rem; flex-wrap:wrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds`}>\n Builds {info?.count ? `(${info.count})` : \"\"}\n </a>\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/settings`}>\n Settings\n </a>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n <div class=\"card card--padded\" style=\"margin-top:1rem;\">\n <h3 style=\"margin:0 0 .4rem;\">Next steps</h3>\n <ol style=\"margin:.4rem 0 0; padding-left:1.2rem; color:var(--text-secondary);\">\n <li>\n Create a project (or run <code>npx @storyshelf/cli create --url http://localhost:3000 --name \"My Storybook\" --token $STORYSHELF_ADMIN_TOKEN</code>, or <code>init --url --slug</code> to write <code>.storybook/storyshelf.json</code>)\n </li>\n <li>\n Generate a token in <strong>Settings → Tokens</strong> and set <code>STORYSHELF_TOKEN</code> in CI.\n </li>\n <li>\n Upload: <code>npx @storyshelf/cli upload</code> (or <code>npx @storyshelf/cli</code> defaults to upload when config exists)\n </li>\n </ol>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ import.meta.url;
4
4
  import { DocumentLayout } from "../ui/document.mjs";
5
5
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
6
6
  //#region src/pages/root.tsx
7
+ /** Landing page introducing capture, diff, and review. */
7
8
  function renderRootPage() {
8
9
  return /* @__PURE__ */ jsxs(DocumentLayout, {
9
10
  title: "Welcome",