@storyshelf/core 0.1.2 → 0.3.1

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 (275) hide show
  1. package/README.md +50 -46
  2. package/dist/adapter/auth.d.mts +4 -4
  3. package/dist/adapter/auth.mjs.map +1 -1
  4. package/dist/{adapters → adapter}/capture-queue.d.mts +6 -4
  5. package/dist/adapter/capture-queue.mjs +4 -0
  6. package/dist/adapter/capture-runner.d.mts +7 -7
  7. package/dist/adapter/database.d.mts +20 -9
  8. package/dist/adapter/database.mjs +60 -1
  9. package/dist/adapter/database.mjs.map +1 -0
  10. package/dist/adapter/git-host/comments.d.mts +41 -0
  11. package/dist/adapter/git-host/comments.mjs +54 -0
  12. package/dist/adapter/git-host/comments.mjs.map +1 -0
  13. package/dist/adapter/git-host/helpers.d.mts +1 -0
  14. package/dist/adapter/git-host/helpers.mjs +1 -0
  15. package/dist/adapter/git-host/helpers.mjs.map +1 -1
  16. package/dist/adapter/git-host.d.mts +3 -7
  17. package/dist/adapter/init.d.mts +58 -0
  18. package/dist/adapter/init.mjs +93 -0
  19. package/dist/adapter/init.mjs.map +1 -0
  20. package/dist/adapter/metadata.d.mts +83 -0
  21. package/dist/adapter/metadata.mjs +4 -0
  22. package/dist/adapter/storage.d.mts +15 -4
  23. package/dist/adapters/webhook-events.mjs +20 -4
  24. package/dist/adapters/webhook-events.mjs.map +1 -1
  25. package/dist/capture/adapter.d.mts +7 -1
  26. package/dist/capture/adapter.mjs +3 -0
  27. package/dist/capture/adapter.mjs.map +1 -1
  28. package/dist/capture/dispatch.mjs +150 -0
  29. package/dist/capture/dispatch.mjs.map +1 -0
  30. package/dist/capture/orchestrator.d.mts +9 -0
  31. package/dist/capture/orchestrator.mjs +11 -44
  32. package/dist/capture/orchestrator.mjs.map +1 -1
  33. package/dist/capture/pipeline.d.mts +4 -1
  34. package/dist/capture/pipeline.mjs +6 -6
  35. package/dist/capture/pipeline.mjs.map +1 -1
  36. package/dist/capture/queue.d.mts +4 -1
  37. package/dist/capture/queue.mjs +3 -1
  38. package/dist/capture/queue.mjs.map +1 -1
  39. package/dist/capture/skip-checks.mjs +76 -0
  40. package/dist/capture/skip-checks.mjs.map +1 -0
  41. package/dist/capture/statics.mjs +124 -0
  42. package/dist/capture/statics.mjs.map +1 -0
  43. package/dist/capture/status-fanout.mjs +1 -0
  44. package/dist/capture/status-fanout.mjs.map +1 -1
  45. package/dist/capture/storybook.d.mts +1 -0
  46. package/dist/capture/storybook.mjs +1 -0
  47. package/dist/capture/storybook.mjs.map +1 -1
  48. package/dist/capture.d.mts +8 -0
  49. package/dist/capture.mjs +9 -0
  50. package/dist/config.d.mts +37 -22
  51. package/dist/config.mjs +28 -2
  52. package/dist/config.mjs.map +1 -1
  53. package/dist/ddl.d.mts +2 -1
  54. package/dist/ddl.mjs +2 -1
  55. package/dist/ddl.mjs.map +1 -1
  56. package/dist/diff/engine.d.mts +8 -0
  57. package/dist/diff/engine.mjs +8 -0
  58. package/dist/diff/engine.mjs.map +1 -1
  59. package/dist/diff/options.d.mts +3 -1
  60. package/dist/diff.d.mts +5 -0
  61. package/dist/diff.mjs +6 -0
  62. package/dist/index.d.mts +34 -24
  63. package/dist/index.mjs +211 -252
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/logger.d.mts +3 -3
  66. package/dist/logger.mjs.map +1 -1
  67. package/dist/middleware/auth-gate.mjs +36 -0
  68. package/dist/middleware/auth-gate.mjs.map +1 -0
  69. package/dist/middleware/csrf.mjs +1 -0
  70. package/dist/middleware/csrf.mjs.map +1 -1
  71. package/dist/middleware/rate-limit.mjs +1 -0
  72. package/dist/middleware/rate-limit.mjs.map +1 -1
  73. package/dist/middleware/request-log.mjs +28 -0
  74. package/dist/middleware/request-log.mjs.map +1 -0
  75. package/dist/middleware/store-scope.mjs +29 -0
  76. package/dist/middleware/store-scope.mjs.map +1 -0
  77. package/dist/models/baseline.mjs +9 -5
  78. package/dist/models/baseline.mjs.map +1 -1
  79. package/dist/models/build.mjs +5 -1
  80. package/dist/models/build.mjs.map +1 -1
  81. package/dist/models/comment.mjs +4 -1
  82. package/dist/models/comment.mjs.map +1 -1
  83. package/dist/models/label.mjs +3 -1
  84. package/dist/models/label.mjs.map +1 -1
  85. package/dist/models/member.mjs +2 -1
  86. package/dist/models/member.mjs.map +1 -1
  87. package/dist/models/project.mjs +2 -1
  88. package/dist/models/project.mjs.map +1 -1
  89. package/dist/models/snapshot.mjs +2 -1
  90. package/dist/models/snapshot.mjs.map +1 -1
  91. package/dist/models/status-config.mjs +2 -1
  92. package/dist/models/status-config.mjs.map +1 -1
  93. package/dist/models/token.mjs +2 -1
  94. package/dist/models/token.mjs.map +1 -1
  95. package/dist/models/webhook.mjs +12 -3
  96. package/dist/models/webhook.mjs.map +1 -1
  97. package/dist/pages/build-detail.mjs +10 -4
  98. package/dist/pages/build-detail.mjs.map +1 -1
  99. package/dist/pages/build-diff-comments.mjs +113 -0
  100. package/dist/pages/build-diff-comments.mjs.map +1 -0
  101. package/dist/pages/build-diff-header.mjs +163 -0
  102. package/dist/pages/build-diff-header.mjs.map +1 -0
  103. package/dist/pages/build-diff-nav.mjs +66 -0
  104. package/dist/pages/build-diff-nav.mjs.map +1 -0
  105. package/dist/pages/build-diff-viewer.mjs +197 -0
  106. package/dist/pages/build-diff-viewer.mjs.map +1 -0
  107. package/dist/pages/build-diff.mjs +66 -413
  108. package/dist/pages/build-diff.mjs.map +1 -1
  109. package/dist/pages/compute-jobs.mjs +3 -1
  110. package/dist/pages/compute-jobs.mjs.map +1 -1
  111. package/dist/pages/label-detail.mjs +4 -3
  112. package/dist/pages/label-detail.mjs.map +1 -1
  113. package/dist/pages/login.mjs +1 -0
  114. package/dist/pages/login.mjs.map +1 -1
  115. package/dist/pages/project-builds.mjs +6 -3
  116. package/dist/pages/project-builds.mjs.map +1 -1
  117. package/dist/pages/project-create.mjs +1 -0
  118. package/dist/pages/project-create.mjs.map +1 -1
  119. package/dist/pages/project-settings.mjs +1 -0
  120. package/dist/pages/project-settings.mjs.map +1 -1
  121. package/dist/pages/projects.mjs +12 -6
  122. package/dist/pages/projects.mjs.map +1 -1
  123. package/dist/pages/root.mjs +7 -3
  124. package/dist/pages/root.mjs.map +1 -1
  125. package/dist/pages/settings-general.mjs +1 -0
  126. package/dist/pages/settings-general.mjs.map +1 -1
  127. package/dist/pages/settings-labels.mjs +2 -1
  128. package/dist/pages/settings-labels.mjs.map +1 -1
  129. package/dist/pages/settings-members.mjs +2 -1
  130. package/dist/pages/settings-members.mjs.map +1 -1
  131. package/dist/pages/settings-status.mjs +3 -1
  132. package/dist/pages/settings-status.mjs.map +1 -1
  133. package/dist/pages/settings-tests.mjs +4 -3
  134. package/dist/pages/settings-tests.mjs.map +1 -1
  135. package/dist/pages/settings-tokens.mjs +1 -0
  136. package/dist/pages/settings-tokens.mjs.map +1 -1
  137. package/dist/pages/settings-webhooks.mjs +1 -0
  138. package/dist/pages/settings-webhooks.mjs.map +1 -1
  139. package/dist/pages/storybook.mjs +38 -1
  140. package/dist/pages/storybook.mjs.map +1 -1
  141. package/dist/{utils/paths.d.mts → paths.d.mts} +5 -0
  142. package/dist/{utils/paths.mjs → paths.mjs} +5 -0
  143. package/dist/paths.mjs.map +1 -0
  144. package/dist/retention/purge.mjs +2 -1
  145. package/dist/retention/purge.mjs.map +1 -1
  146. package/dist/routers/admin.mjs +2 -1
  147. package/dist/routers/admin.mjs.map +1 -1
  148. package/dist/routers/assets.mjs +1 -0
  149. package/dist/routers/assets.mjs.map +1 -1
  150. package/dist/routers/auth.mjs +1 -0
  151. package/dist/routers/auth.mjs.map +1 -1
  152. package/dist/routers/builds.handlers.mjs +114 -19
  153. package/dist/routers/builds.handlers.mjs.map +1 -1
  154. package/dist/routers/builds.mjs +55 -34
  155. package/dist/routers/builds.mjs.map +1 -1
  156. package/dist/routers/comments.mjs +1 -0
  157. package/dist/routers/comments.mjs.map +1 -1
  158. package/dist/routers/health.mjs +137 -0
  159. package/dist/routers/health.mjs.map +1 -0
  160. package/dist/routers/helpers.mjs +8 -2
  161. package/dist/routers/helpers.mjs.map +1 -1
  162. package/dist/routers/htmx.mjs +2 -0
  163. package/dist/routers/htmx.mjs.map +1 -1
  164. package/dist/routers/labels.mjs +2 -1
  165. package/dist/routers/labels.mjs.map +1 -1
  166. package/dist/routers/media.mjs +3 -2
  167. package/dist/routers/media.mjs.map +1 -1
  168. package/dist/routers/members.mjs +1 -0
  169. package/dist/routers/members.mjs.map +1 -1
  170. package/dist/routers/projects.mjs +2 -1
  171. package/dist/routers/projects.mjs.map +1 -1
  172. package/dist/routers/schemas.mjs +25 -0
  173. package/dist/routers/schemas.mjs.map +1 -1
  174. package/dist/routers/settings-general.mjs +95 -0
  175. package/dist/routers/settings-general.mjs.map +1 -0
  176. package/dist/routers/settings-labels.mjs +49 -0
  177. package/dist/routers/settings-labels.mjs.map +1 -0
  178. package/dist/routers/settings-members.mjs +30 -0
  179. package/dist/routers/settings-members.mjs.map +1 -0
  180. package/dist/routers/settings-status.mjs +73 -0
  181. package/dist/routers/settings-status.mjs.map +1 -0
  182. package/dist/routers/settings-tokens.mjs +40 -0
  183. package/dist/routers/settings-tokens.mjs.map +1 -0
  184. package/dist/routers/settings-webhooks.mjs +63 -0
  185. package/dist/routers/settings-webhooks.mjs.map +1 -0
  186. package/dist/routers/settings.handlers.mjs +93 -0
  187. package/dist/routers/settings.handlers.mjs.map +1 -0
  188. package/dist/routers/settings.mjs +14 -246
  189. package/dist/routers/settings.mjs.map +1 -1
  190. package/dist/routers/snapshots.mjs +2 -1
  191. package/dist/routers/snapshots.mjs.map +1 -1
  192. package/dist/routers/status-configs.mjs +2 -1
  193. package/dist/routers/status-configs.mjs.map +1 -1
  194. package/dist/routers/storybook.mjs +12 -4
  195. package/dist/routers/storybook.mjs.map +1 -1
  196. package/dist/routers/tokens.mjs +2 -1
  197. package/dist/routers/tokens.mjs.map +1 -1
  198. package/dist/routers/ui.mjs +5 -4
  199. package/dist/routers/ui.mjs.map +1 -1
  200. package/dist/routers/webhooks.mjs +7 -6
  201. package/dist/routers/webhooks.mjs.map +1 -1
  202. package/dist/schema/baseline.d.mts +18 -0
  203. package/dist/schema/baseline.mjs +22 -0
  204. package/dist/schema/baseline.mjs.map +1 -0
  205. package/dist/schema/build.d.mts +26 -0
  206. package/dist/schema/build.mjs +29 -0
  207. package/dist/schema/build.mjs.map +1 -0
  208. package/dist/schema/comment.d.mts +19 -0
  209. package/dist/schema/comment.mjs +26 -0
  210. package/dist/schema/comment.mjs.map +1 -0
  211. package/dist/schema/label.d.mts +25 -0
  212. package/dist/schema/label.mjs +30 -0
  213. package/dist/schema/label.mjs.map +1 -0
  214. package/dist/schema/member.d.mts +15 -0
  215. package/dist/schema/member.mjs +19 -0
  216. package/dist/schema/member.mjs.map +1 -0
  217. package/dist/schema/project.d.mts +34 -0
  218. package/dist/schema/project.mjs +25 -0
  219. package/dist/schema/project.mjs.map +1 -0
  220. package/dist/schema/snapshot.d.mts +30 -0
  221. package/dist/schema/snapshot.mjs +34 -0
  222. package/dist/schema/snapshot.mjs.map +1 -0
  223. package/dist/schema/status-config.d.mts +16 -0
  224. package/dist/schema/status-config.mjs +20 -0
  225. package/dist/schema/status-config.mjs.map +1 -0
  226. package/dist/schema/token.d.mts +15 -0
  227. package/dist/schema/token.mjs +19 -0
  228. package/dist/schema/token.mjs.map +1 -0
  229. package/dist/schema/user.d.mts +17 -0
  230. package/dist/schema/user.mjs +19 -0
  231. package/dist/schema/user.mjs.map +1 -0
  232. package/dist/schema/webhook.d.mts +16 -0
  233. package/dist/schema/webhook.mjs +20 -0
  234. package/dist/schema/webhook.mjs.map +1 -0
  235. package/dist/schema.d.mts +52 -4433
  236. package/dist/schema.mjs +37 -139
  237. package/dist/schema.mjs.map +1 -1
  238. package/dist/store.mjs +2 -0
  239. package/dist/store.mjs.map +1 -1
  240. package/dist/types.d.mts +13 -2
  241. package/dist/types.mjs +8 -0
  242. package/dist/types.mjs.map +1 -1
  243. package/dist/ui/document.mjs +1 -0
  244. package/dist/ui/document.mjs.map +1 -1
  245. package/dist/ui/feedback.mjs +24 -0
  246. package/dist/ui/feedback.mjs.map +1 -0
  247. package/dist/ui/{components.mjs → forms.mjs} +6 -16
  248. package/dist/ui/forms.mjs.map +1 -0
  249. package/dist/ui/styles.mjs +1 -0
  250. package/dist/ui/styles.mjs.map +1 -1
  251. package/dist/ui/theme.mjs +2 -0
  252. package/dist/ui/theme.mjs.map +1 -1
  253. package/dist/urls.d.mts +8 -0
  254. package/dist/urls.mjs +7 -0
  255. package/dist/urls.mjs.map +1 -1
  256. package/dist/utils/hash.mjs +3 -0
  257. package/dist/utils/hash.mjs.map +1 -1
  258. package/dist/utils/ulid.mjs +2 -0
  259. package/dist/utils/ulid.mjs.map +1 -1
  260. package/package.json +47 -45
  261. package/dist/adapters/metadata.d.mts +0 -24
  262. package/dist/models/baseline.d.mts +0 -20
  263. package/dist/models/build.d.mts +0 -50
  264. package/dist/models/comment.d.mts +0 -20
  265. package/dist/models/label.d.mts +0 -49
  266. package/dist/models/member.d.mts +0 -27
  267. package/dist/models/project.d.mts +0 -42
  268. package/dist/models/snapshot.d.mts +0 -46
  269. package/dist/models/token.d.mts +0 -33
  270. package/dist/models/webhook.d.mts +0 -37
  271. package/dist/retention/purge.d.mts +0 -28
  272. package/dist/ui/components.mjs.map +0 -1
  273. package/dist/ui/document.d.mts +0 -8
  274. package/dist/utils/paths.mjs.map +0 -1
  275. package/dist/utils/ulid.d.mts +0 -8
package/dist/schema.mjs CHANGED
@@ -1,145 +1,43 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { index, integer, real, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
5
- //#region src/schema.ts
6
- const projects = sqliteTable("projects", {
7
- id: text("id").primaryKey(),
8
- name: text("name").notNull(),
9
- slug: text("slug").notNull(),
10
- gitRepository: text("git_repository"),
11
- gitDefaultBranch: text("git_default_branch").notNull().default("main"),
12
- pixelThreshold: real("pixel_threshold").notNull().default(.1),
13
- maxDiffRatio: real("max_diff_ratio").notNull().default(.01),
14
- publicBranchRegex: text("public_branch_regex"),
15
- storybookMeta: text("storybook_meta").$type().default(null),
16
- executePlay: integer("execute_play", { mode: "boolean" }).notNull().default(false),
17
- playTimeoutMs: integer("play_timeout_ms").notNull().default(1e4),
18
- createdAt: text("created_at").notNull(),
19
- updatedAt: text("updated_at").notNull()
20
- });
21
- const projectStatusConfigs = sqliteTable("project_status_configs", {
22
- id: text("id").primaryKey(),
23
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
24
- provider: text("provider").notNull(),
25
- config: text("config").notNull(),
26
- tokenEncrypted: text("token_encrypted").notNull(),
27
- createdAt: text("created_at").notNull(),
28
- updatedAt: text("updated_at").notNull()
29
- });
30
- const builds = sqliteTable("builds", {
31
- id: text("id").primaryKey(),
32
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
33
- gitSha: text("git_sha").notNull(),
34
- gitBranch: text("git_branch").notNull(),
35
- isDefault: integer("is_default", { mode: "boolean" }).notNull().default(false),
36
- authorEmail: text("author_email"),
37
- authorName: text("author_name"),
38
- message: text("message"),
39
- public: integer("public", { mode: "boolean" }).notNull().default(false),
40
- status: text("status").$type().notNull().default("pending"),
41
- snapshotCount: integer("snapshot_count").notNull().default(0),
42
- changedCount: integer("changed_count").notNull().default(0),
43
- approvedCount: integer("approved_count").notNull().default(0),
44
- rejectedCount: integer("rejected_count").notNull().default(0),
45
- createdAt: text("created_at").notNull(),
46
- updatedAt: text("updated_at").notNull()
47
- }, (t) => [uniqueIndex("builds_project_gitsha_idx").on(t.projectId, t.gitSha), index("builds_git_branch_idx").on(t.gitBranch)]);
48
- const snapshots = sqliteTable("snapshots", {
49
- id: text("id").primaryKey(),
50
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
51
- buildId: text("build_id").notNull().references(() => builds.id, { onDelete: "cascade" }),
52
- storyId: text("story_id").notNull(),
53
- storyName: text("story_name").notNull(),
54
- storyTitle: text("story_title").notNull(),
55
- storyImportPath: text("story_import_path"),
56
- viewportName: text("viewport_name").notNull().default("desktop"),
57
- viewportWidth: integer("viewport_width").notNull().default(1280),
58
- viewportHeight: integer("viewport_height").notNull().default(720),
59
- screenshotPath: text("screenshot_path").notNull(),
60
- diffPath: text("diff_path"),
61
- diffPixels: integer("diff_pixels"),
62
- diffRatio: real("diff_ratio"),
63
- diffPassed: integer("diff_passed", { mode: "boolean" }),
64
- status: text("status").$type().notNull().default("pending"),
65
- reviewedBy: text("reviewed_by"),
66
- reviewedAt: text("reviewed_at"),
67
- createdAt: text("created_at").notNull(),
68
- updatedAt: text("updated_at").notNull()
69
- }, (t) => [uniqueIndex("snapshots_build_story_viewport_idx").on(t.buildId, t.storyId, t.viewportName), index("snapshots_build_id_idx").on(t.buildId)]);
70
- const baselines = sqliteTable("baselines", {
71
- id: text("id").primaryKey(),
72
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
73
- storyId: text("story_id").notNull(),
74
- viewportName: text("viewport_name").notNull().default("desktop"),
75
- branch: text("branch").notNull(),
76
- snapshotId: text("snapshot_id"),
77
- screenshotPath: text("screenshot_path").notNull(),
78
- createdAt: text("created_at").notNull(),
79
- updatedAt: text("updated_at").notNull()
80
- }, (t) => [uniqueIndex("baselines_project_story_viewport_branch_idx").on(t.projectId, t.storyId, t.viewportName, t.branch), index("baselines_project_story_idx").on(t.projectId, t.storyId)]);
81
- const comments = sqliteTable("comments", {
82
- id: text("id").primaryKey(),
83
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
84
- buildId: text("build_id").notNull().references(() => builds.id, { onDelete: "cascade" }),
85
- snapshotId: text("snapshot_id").references(() => snapshots.id, { onDelete: "cascade" }),
86
- userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
87
- body: text("body").notNull(),
88
- parentId: text("parent_id"),
89
- resolved: integer("resolved", { mode: "boolean" }).notNull().default(false),
90
- createdAt: text("created_at").notNull(),
91
- updatedAt: text("updated_at").notNull()
92
- }, (t) => [index("comments_build_id_idx").on(t.buildId)]);
93
- const labelTypes = sqliteTable("label_types", {
94
- id: text("id").primaryKey(),
95
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
96
- key: text("key").notNull(),
97
- name: text("name").notNull(),
98
- linkTemplate: text("link_template"),
99
- color: text("color"),
100
- createdAt: text("created_at").notNull()
101
- }, (t) => [uniqueIndex("label_types_project_key_idx").on(t.projectId, t.key)]);
102
- const buildLabels = sqliteTable("build_labels", {
103
- id: text("id").primaryKey(),
104
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
105
- buildId: text("build_id").notNull().references(() => builds.id, { onDelete: "cascade" }),
106
- typeKey: text("type_key").notNull(),
107
- value: text("value").notNull(),
108
- createdAt: text("created_at").notNull()
109
- }, (t) => [uniqueIndex("build_labels_build_type_value_idx").on(t.buildId, t.typeKey, t.value)]);
110
- const tokens = sqliteTable("tokens", {
111
- id: text("id").primaryKey(),
112
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
113
- name: text("name").notNull(),
114
- hash: text("hash").notNull(),
115
- lastUsedAt: text("last_used_at"),
116
- createdAt: text("created_at").notNull()
117
- });
118
- const webhooks = sqliteTable("webhooks", {
119
- id: text("id").primaryKey(),
120
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
121
- url: text("url").notNull(),
122
- secret: text("secret").notNull(),
123
- events: text("events"),
124
- createdAt: text("created_at").notNull(),
125
- updatedAt: text("updated_at").notNull()
126
- });
127
- const users = sqliteTable("users", {
128
- id: text("id").primaryKey(),
129
- email: text("email").notNull(),
130
- name: text("name").notNull(),
131
- avatarUrl: text("avatar_url"),
132
- role: text("role").$type().notNull().default("member"),
133
- lastLoginAt: text("last_login_at"),
134
- createdAt: text("created_at").notNull()
135
- });
136
- const projectMembers = sqliteTable("project_members", {
137
- id: text("id").primaryKey(),
138
- projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
139
- userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
140
- role: text("role").$type().notNull().default("viewer"),
141
- createdAt: text("created_at").notNull()
142
- }, (t) => [uniqueIndex("project_members_project_user_idx").on(t.projectId, t.userId)]);
4
+ import { projects as projects$1 } from "./schema/project.mjs";
5
+ import { builds as builds$1 } from "./schema/build.mjs";
6
+ import { buildLabels as buildLabels$1, labelTypes as labelTypes$1 } from "./schema/label.mjs";
7
+ import { snapshots as snapshots$1 } from "./schema/snapshot.mjs";
8
+ import { webhooks as webhooks$1 } from "./schema/webhook.mjs";
9
+ import { baselines as baselines$1 } from "./schema/baseline.mjs";
10
+ import { projectStatusConfigs as projectStatusConfigs$1 } from "./schema/status-config.mjs";
11
+ import { users as users$1 } from "./schema/user.mjs";
12
+ import { projectMembers as projectMembers$1 } from "./schema/member.mjs";
13
+ import { tokens as tokens$1 } from "./schema/token.mjs";
14
+ import { comments as comments$1 } from "./schema/comment.mjs";
15
+ //#region src/schema/index.ts
16
+ /** Projects table (one row per Storybook project). */
17
+ const projects = projects$1;
18
+ /** Project status-configs table (merge-gate configurations). */
19
+ const projectStatusConfigs = projectStatusConfigs$1;
20
+ /** Builds table (one row per uploaded Storybook build). */
21
+ const builds = builds$1;
22
+ /** Snapshots table (one row per captured story screenshot). */
23
+ const snapshots = snapshots$1;
24
+ /** Baselines table (accepted reference snapshots per branch). */
25
+ const baselines = baselines$1;
26
+ /** Comments table (review comments on snapshots). */
27
+ const comments = comments$1;
28
+ /** Label-types table (project-defined build label vocabularies). */
29
+ const labelTypes = labelTypes$1;
30
+ /** Build-labels table (labels attached to builds). */
31
+ const buildLabels = buildLabels$1;
32
+ /** Tokens table (CI token hashes per project). */
33
+ const tokens = tokens$1;
34
+ /** Webhooks table (outgoing build-event subscriptions). */
35
+ const webhooks = webhooks$1;
36
+ /** Users table (dashboard members). */
37
+ const users = users$1;
38
+ /** Project-members table (per-project memberships and roles). */
39
+ const projectMembers = projectMembers$1;
40
+ /** Full Drizzle schema object passed to the database client. */
143
41
  const schema = {
144
42
  projects,
145
43
  projectStatusConfigs,
@@ -1 +1 @@
1
- {"version":3,"file":"schema.mjs","names":[],"sources":["../src/schema.ts"],"sourcesContent":["import { index, integer, real, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\n\nimport type { BuildStatus, ProjectRole, SiteRole, SnapshotStatus } from \"./types.ts\";\n\nexport interface StorybookMeta {\n framework?: { name?: string; options?: unknown };\n addons?: string[];\n storiesGlobs?: string[];\n staticDirs?: string[];\n packagePath?: string;\n previewParameters?: Record<string, unknown>;\n}\n\nexport const projects = sqliteTable(\"projects\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull(),\n gitRepository: text(\"git_repository\"),\n gitDefaultBranch: text(\"git_default_branch\").notNull().default(\"main\"),\n pixelThreshold: real(\"pixel_threshold\").notNull().default(0.1),\n maxDiffRatio: real(\"max_diff_ratio\").notNull().default(0.01),\n publicBranchRegex: text(\"public_branch_regex\"),\n storybookMeta: text(\"storybook_meta\").$type<string | null | undefined>().default(null),\n executePlay: integer(\"execute_play\", { mode: \"boolean\" }).notNull().default(false),\n playTimeoutMs: integer(\"play_timeout_ms\").notNull().default(10_000),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n});\n\nexport const projectStatusConfigs = sqliteTable(\"project_status_configs\", {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n provider: text(\"provider\").notNull(),\n config: text(\"config\").notNull(),\n tokenEncrypted: text(\"token_encrypted\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n});\n\nexport const builds = sqliteTable(\n \"builds\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n gitSha: text(\"git_sha\").notNull(),\n gitBranch: text(\"git_branch\").notNull(),\n isDefault: integer(\"is_default\", { mode: \"boolean\" }).notNull().default(false),\n authorEmail: text(\"author_email\"),\n authorName: text(\"author_name\"),\n message: text(\"message\"),\n public: integer(\"public\", { mode: \"boolean\" }).notNull().default(false),\n status: text(\"status\").$type<BuildStatus>().notNull().default(\"pending\"),\n snapshotCount: integer(\"snapshot_count\").notNull().default(0),\n changedCount: integer(\"changed_count\").notNull().default(0),\n approvedCount: integer(\"approved_count\").notNull().default(0),\n rejectedCount: integer(\"rejected_count\").notNull().default(0),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"builds_project_gitsha_idx\").on(t.projectId, t.gitSha),\n index(\"builds_git_branch_idx\").on(t.gitBranch),\n ],\n);\n\nexport const snapshots = sqliteTable(\n \"snapshots\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n buildId: text(\"build_id\")\n .notNull()\n .references(() => builds.id, { onDelete: \"cascade\" }),\n storyId: text(\"story_id\").notNull(),\n storyName: text(\"story_name\").notNull(),\n storyTitle: text(\"story_title\").notNull(),\n storyImportPath: text(\"story_import_path\"),\n viewportName: text(\"viewport_name\").notNull().default(\"desktop\"),\n viewportWidth: integer(\"viewport_width\").notNull().default(1280),\n viewportHeight: integer(\"viewport_height\").notNull().default(720),\n screenshotPath: text(\"screenshot_path\").notNull(),\n diffPath: text(\"diff_path\"),\n diffPixels: integer(\"diff_pixels\"),\n diffRatio: real(\"diff_ratio\"),\n diffPassed: integer(\"diff_passed\", { mode: \"boolean\" }),\n status: text(\"status\").$type<SnapshotStatus>().notNull().default(\"pending\"),\n reviewedBy: text(\"reviewed_by\"),\n reviewedAt: text(\"reviewed_at\"),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"snapshots_build_story_viewport_idx\").on(t.buildId, t.storyId, t.viewportName),\n index(\"snapshots_build_id_idx\").on(t.buildId),\n ],\n);\n\nexport const baselines = sqliteTable(\n \"baselines\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n storyId: text(\"story_id\").notNull(),\n viewportName: text(\"viewport_name\").notNull().default(\"desktop\"),\n branch: text(\"branch\").notNull(),\n snapshotId: text(\"snapshot_id\"),\n screenshotPath: text(\"screenshot_path\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"baselines_project_story_viewport_branch_idx\").on(t.projectId, t.storyId, t.viewportName, t.branch),\n index(\"baselines_project_story_idx\").on(t.projectId, t.storyId),\n ],\n);\n\nexport const comments = sqliteTable(\n \"comments\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n buildId: text(\"build_id\")\n .notNull()\n .references(() => builds.id, { onDelete: \"cascade\" }),\n snapshotId: text(\"snapshot_id\").references(() => snapshots.id, { onDelete: \"cascade\" }),\n userId: text(\"user_id\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n body: text(\"body\").notNull(),\n parentId: text(\"parent_id\"),\n resolved: integer(\"resolved\", { mode: \"boolean\" }).notNull().default(false),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [index(\"comments_build_id_idx\").on(t.buildId)],\n);\n\nexport const labelTypes = sqliteTable(\n \"label_types\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n key: text(\"key\").notNull(),\n name: text(\"name\").notNull(),\n linkTemplate: text(\"link_template\"),\n color: text(\"color\"),\n createdAt: text(\"created_at\").notNull(),\n },\n (t) => [uniqueIndex(\"label_types_project_key_idx\").on(t.projectId, t.key)],\n);\n\nexport const buildLabels = sqliteTable(\n \"build_labels\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n buildId: text(\"build_id\")\n .notNull()\n .references(() => builds.id, { onDelete: \"cascade\" }),\n typeKey: text(\"type_key\").notNull(),\n value: text(\"value\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n },\n (t) => [uniqueIndex(\"build_labels_build_type_value_idx\").on(t.buildId, t.typeKey, t.value)],\n);\n\nexport const tokens = sqliteTable(\"tokens\", {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n name: text(\"name\").notNull(),\n hash: text(\"hash\").notNull(),\n lastUsedAt: text(\"last_used_at\"),\n createdAt: text(\"created_at\").notNull(),\n});\n\nexport const webhooks = sqliteTable(\"webhooks\", {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n url: text(\"url\").notNull(),\n secret: text(\"secret\").notNull(),\n events: text(\"events\"),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n});\n\nexport const users = sqliteTable(\"users\", {\n id: text(\"id\").primaryKey(),\n email: text(\"email\").notNull(),\n name: text(\"name\").notNull(),\n avatarUrl: text(\"avatar_url\"),\n role: text(\"role\").$type<SiteRole>().notNull().default(\"member\"),\n lastLoginAt: text(\"last_login_at\"),\n createdAt: text(\"created_at\").notNull(),\n});\n\nexport const projectMembers = sqliteTable(\n \"project_members\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n userId: text(\"user_id\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n role: text(\"role\").$type<ProjectRole>().notNull().default(\"viewer\"),\n createdAt: text(\"created_at\").notNull(),\n },\n (t) => [uniqueIndex(\"project_members_project_user_idx\").on(t.projectId, t.userId)],\n);\n\nexport const schema = {\n projects,\n projectStatusConfigs,\n builds,\n snapshots,\n baselines,\n comments,\n labelTypes,\n buildLabels,\n tokens,\n webhooks,\n users,\n projectMembers,\n};\n\nexport type Schema = typeof schema;\n\nexport type Project = typeof projects.$inferSelect;\nexport type ProjectStatusConfig = typeof projectStatusConfigs.$inferSelect;\nexport type Build = typeof builds.$inferSelect;\nexport type Snapshot = typeof snapshots.$inferSelect;\nexport type Baseline = typeof baselines.$inferSelect;\nexport type Comment = typeof comments.$inferSelect;\nexport type LabelType = typeof labelTypes.$inferSelect;\nexport type BuildLabel = typeof buildLabels.$inferSelect;\nexport type Token = typeof tokens.$inferSelect;\nexport type Webhook = typeof webhooks.$inferSelect;\nexport type User = typeof users.$inferSelect;\nexport type ProjectMember = typeof projectMembers.$inferSelect;\n"],"mappings":"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"}
1
+ {"version":3,"file":"schema.mjs","names":["projectsTable","projectStatusConfigsTable","buildsTable","snapshotsTable","baselinesTable","commentsTable","labelTypesTable","buildLabelsTable","tokensTable","webhooksTable","usersTable","projectMembersTable"],"sources":["../src/schema/index.ts"],"sourcesContent":["/**\n * Database schema: table handles, row types, and the schema object passed\n * to the database client.\n */\nimport type { AnySQLiteTable } from \"drizzle-orm/sqlite-core\";\n\nimport { baselines as baselinesTable } from \"./baseline.ts\";\nimport { buildLabels as buildLabelsTable, labelTypes as labelTypesTable } from \"./label.ts\";\nimport { builds as buildsTable } from \"./build.ts\";\nimport { comments as commentsTable } from \"./comment.ts\";\nimport { projectMembers as projectMembersTable } from \"./member.ts\";\nimport { projects as projectsTable } from \"./project.ts\";\nimport { projectStatusConfigs as projectStatusConfigsTable } from \"./status-config.ts\";\nimport { snapshots as snapshotsTable } from \"./snapshot.ts\";\nimport { tokens as tokensTable } from \"./token.ts\";\nimport { users as usersTable } from \"./user.ts\";\nimport { webhooks as webhooksTable } from \"./webhook.ts\";\n\n/**\n * Database schema: table handles, row types, and the schema object passed\n * to the database client.\n *\n * Table handles are explicitly typed (JSR fast-check); exact column types\n * live on the narrow per-entity definitions, and the row interfaces are\n * pinned to them by `schema-types.test.ts`.\n */\nexport type { Baseline } from \"./baseline.ts\";\nexport type { Build } from \"./build.ts\";\nexport type { Comment } from \"./comment.ts\";\nexport type { BuildLabel, LabelType } from \"./label.ts\";\nexport type { ProjectMember } from \"./member.ts\";\nexport type { Project, StorybookMeta } from \"./project.ts\";\nexport type { ProjectStatusConfig } from \"./status-config.ts\";\nexport type { Snapshot } from \"./snapshot.ts\";\nexport type { Token } from \"./token.ts\";\nexport type { User } from \"./user.ts\";\nexport type { Webhook } from \"./webhook.ts\";\n\n/** Projects table (one row per Storybook project). */\nexport const projects: AnySQLiteTable = projectsTable;\n/** Project status-configs table (merge-gate configurations). */\nexport const projectStatusConfigs: AnySQLiteTable = projectStatusConfigsTable;\n/** Builds table (one row per uploaded Storybook build). */\nexport const builds: AnySQLiteTable = buildsTable;\n/** Snapshots table (one row per captured story screenshot). */\nexport const snapshots: AnySQLiteTable = snapshotsTable;\n/** Baselines table (accepted reference snapshots per branch). */\nexport const baselines: AnySQLiteTable = baselinesTable;\n/** Comments table (review comments on snapshots). */\nexport const comments: AnySQLiteTable = commentsTable;\n/** Label-types table (project-defined build label vocabularies). */\nexport const labelTypes: AnySQLiteTable = labelTypesTable;\n/** Build-labels table (labels attached to builds). */\nexport const buildLabels: AnySQLiteTable = buildLabelsTable;\n/** Tokens table (CI token hashes per project). */\nexport const tokens: AnySQLiteTable = tokensTable;\n/** Webhooks table (outgoing build-event subscriptions). */\nexport const webhooks: AnySQLiteTable = webhooksTable;\n/** Users table (dashboard members). */\nexport const users: AnySQLiteTable = usersTable;\n/** Project-members table (per-project memberships and roles). */\nexport const projectMembers: AnySQLiteTable = projectMembersTable;\n\n/** Full Drizzle schema object passed to the database client. */\nexport const schema = {\n projects,\n projectStatusConfigs,\n builds,\n snapshots,\n baselines,\n comments,\n labelTypes,\n buildLabels,\n tokens,\n webhooks,\n users,\n projectMembers,\n};\n\n/** The full database schema type. */\nexport type Schema = typeof schema;\n"],"mappings":";;;;;;;;;;;;;;;;AAuCA,MAAa,WAA2BA;;AAExC,MAAa,uBAAuCC;;AAEpD,MAAa,SAAyBC;;AAEtC,MAAa,YAA4BC;;AAEzC,MAAa,YAA4BC;;AAEzC,MAAa,WAA2BC;;AAExC,MAAa,aAA6BC;;AAE1C,MAAa,cAA8BC;;AAE3C,MAAa,SAAyBC;;AAEtC,MAAa,WAA2BC;;AAExC,MAAa,QAAwBC;;AAErC,MAAa,iBAAiCC;;AAG9C,MAAa,SAAS;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
package/dist/store.mjs CHANGED
@@ -4,9 +4,11 @@ import.meta.url;
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  //#region src/store.ts
6
6
  const storage = new AsyncLocalStorage();
7
+ /** Run a function with the given request store in scope. */
7
8
  function runWithStore(store, fn) {
8
9
  return storage.run(store, fn);
9
10
  }
11
+ /** Return the current request store, throwing outside a request scope. */
10
12
  function getStore() {
11
13
  const store = storage.getStore();
12
14
  if (!store) throw new Error("Store not available. Use runWithStore().");
@@ -1 +1 @@
1
- {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type { Logger } from \"pino\";\n\nimport type { AuthUser } from \"./adapters/auth.ts\";\nimport type { CaptureQueue } from \"./adapters/capture-queue.ts\";\nimport type { DatabaseAdapter } from \"./adapters/database.ts\";\nimport type { GitHostProvider } from \"./adapters/git-host/index.ts\";\nimport type { StorageAdapter } from \"./adapters/storage.ts\";\nimport type { ShelfConfig, UIConfig } from \"./config.ts\";\n\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n gitHosts: GitHostProvider[];\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n"],"mappings":";;;;;AAwBA,MAAM,UAAU,IAAI,kBAAyB;AAE7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;AAEA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT"}
1
+ {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { Logger } from \"pino\";\nimport type { AuthUser } from \"./adapters/auth.ts\";\nimport type { CaptureQueue } from \"./adapters/capture-queue.ts\";\nimport type { DatabaseAdapter } from \"./adapters/database.ts\";\nimport type { GitHostProvider } from \"./adapters/git-host/index.ts\";\nimport type { StorageAdapter } from \"./adapters/storage.ts\";\nimport type { ShelfConfig, UIConfig } from \"./config.ts\";\n\n/** Request-scoped dependencies and session state. */\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n gitHosts: GitHostProvider[];\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\n/** Run a function with the given request store in scope. */\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\n/** Return the current request store, throwing outside a request scope. */\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n"],"mappings":";;;;;AAuBA,MAAM,UAAU,IAAI,kBAAyB;;AAG7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT"}
package/dist/types.d.mts CHANGED
@@ -1,19 +1,30 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { Logger } from "./logger.mjs";
4
3
  //#region src/types.d.ts
4
+ /** All lifecycle statuses a build can carry. */
5
5
  declare const BUILD_STATUSES: readonly ["pending", "capturing", "comparing", "reviewing", "approved", "rejected", "failed"];
6
+ /** Lifecycle status of a build. */
6
7
  type BuildStatus = (typeof BUILD_STATUSES)[number];
8
+ /** All review statuses a snapshot can carry. */
7
9
  declare const SNAPSHOT_STATUSES: readonly ["pending", "new", "unchanged", "changed", "approved", "rejected"];
10
+ /** Review status of a snapshot. */
8
11
  type SnapshotStatus = (typeof SNAPSHOT_STATUSES)[number];
12
+ /** Build statuses eligible for retention purging. */
9
13
  declare const TERMINAL_BUILD_STATUSES: readonly BuildStatus[];
14
+ /** All site-wide user roles. */
10
15
  declare const SITE_ROLES: readonly ["admin", "member"];
16
+ /** Site-wide role of a user. */
11
17
  type SiteRole = (typeof SITE_ROLES)[number];
18
+ /** All per-project membership roles. */
12
19
  declare const PROJECT_ROLES: readonly ["admin", "approver", "developer", "viewer"];
20
+ /** Per-project role of a member. */
13
21
  type ProjectRole = (typeof PROJECT_ROLES)[number];
22
+ /** Label type keys seeded for every new project. */
14
23
  declare const SEEDED_LABEL_KEYS: readonly ["branch", "persistent", "pr", "mr", "jira", "linear", "figma", "custom"];
24
+ /** Label type keys reserved for internal use. */
15
25
  declare const RESERVED_LABEL_KEYS: readonly ["build"];
26
+ /** Label key marking a build as exempt from retention purging. */
16
27
  declare const PERSISTENT_LABEL_KEY = "persistent";
17
28
  //#endregion
18
- export { BUILD_STATUSES, BuildStatus, type Logger, PERSISTENT_LABEL_KEY, PROJECT_ROLES, ProjectRole, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SITE_ROLES, SNAPSHOT_STATUSES, SiteRole, SnapshotStatus, TERMINAL_BUILD_STATUSES };
29
+ export { BUILD_STATUSES, BuildStatus, PERSISTENT_LABEL_KEY, PROJECT_ROLES, ProjectRole, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SITE_ROLES, SNAPSHOT_STATUSES, SiteRole, SnapshotStatus, TERMINAL_BUILD_STATUSES };
19
30
  //# sourceMappingURL=types.d.mts.map
package/dist/types.mjs CHANGED
@@ -2,6 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  //#region src/types.ts
5
+ /** All lifecycle statuses a build can carry. */
5
6
  const BUILD_STATUSES = [
6
7
  "pending",
7
8
  "capturing",
@@ -11,6 +12,7 @@ const BUILD_STATUSES = [
11
12
  "rejected",
12
13
  "failed"
13
14
  ];
15
+ /** All review statuses a snapshot can carry. */
14
16
  const SNAPSHOT_STATUSES = [
15
17
  "pending",
16
18
  "new",
@@ -19,18 +21,22 @@ const SNAPSHOT_STATUSES = [
19
21
  "approved",
20
22
  "rejected"
21
23
  ];
24
+ /** Build statuses eligible for retention purging. */
22
25
  const TERMINAL_BUILD_STATUSES = [
23
26
  "approved",
24
27
  "rejected",
25
28
  "failed"
26
29
  ];
30
+ /** All site-wide user roles. */
27
31
  const SITE_ROLES = ["admin", "member"];
32
+ /** All per-project membership roles. */
28
33
  const PROJECT_ROLES = [
29
34
  "admin",
30
35
  "approver",
31
36
  "developer",
32
37
  "viewer"
33
38
  ];
39
+ /** Label type keys seeded for every new project. */
34
40
  const SEEDED_LABEL_KEYS = [
35
41
  "branch",
36
42
  "persistent",
@@ -41,7 +47,9 @@ const SEEDED_LABEL_KEYS = [
41
47
  "figma",
42
48
  "custom"
43
49
  ];
50
+ /** Label type keys reserved for internal use. */
44
51
  const RESERVED_LABEL_KEYS = ["build"];
52
+ /** Label key marking a build as exempt from retention purging. */
45
53
  const PERSISTENT_LABEL_KEY = "persistent";
46
54
  //#endregion
47
55
  export { BUILD_STATUSES, PERSISTENT_LABEL_KEY, PROJECT_ROLES, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SITE_ROLES, SNAPSHOT_STATUSES, TERMINAL_BUILD_STATUSES };
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["export const BUILD_STATUSES = [\n \"pending\",\n \"capturing\",\n \"comparing\",\n \"reviewing\",\n \"approved\",\n \"rejected\",\n \"failed\",\n] as const;\n\nexport type BuildStatus = (typeof BUILD_STATUSES)[number];\n\nexport const SNAPSHOT_STATUSES = [\"pending\", \"new\", \"unchanged\", \"changed\", \"approved\", \"rejected\"] as const;\n\nexport type SnapshotStatus = (typeof SNAPSHOT_STATUSES)[number];\n\nexport const TERMINAL_BUILD_STATUSES: readonly BuildStatus[] = [\"approved\", \"rejected\", \"failed\"];\n\nexport const SITE_ROLES = [\"admin\", \"member\"] as const;\n\nexport type SiteRole = (typeof SITE_ROLES)[number];\n\nexport const PROJECT_ROLES = [\"admin\", \"approver\", \"developer\", \"viewer\"] as const;\n\nexport type ProjectRole = (typeof PROJECT_ROLES)[number];\n\nexport const SEEDED_LABEL_KEYS = [\"branch\", \"persistent\", \"pr\", \"mr\", \"jira\", \"linear\", \"figma\", \"custom\"] as const;\n\nexport const RESERVED_LABEL_KEYS = [\"build\"] as const;\n\nexport const PERSISTENT_LABEL_KEY = \"persistent\";\n\nexport type { Logger } from \"./logger.ts\";\n"],"mappings":";;;;AAAA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,oBAAoB;CAAC;CAAW;CAAO;CAAa;CAAW;CAAY;AAAU;AAIlG,MAAa,0BAAkD;CAAC;CAAY;CAAY;AAAQ;AAEhG,MAAa,aAAa,CAAC,SAAS,QAAQ;AAI5C,MAAa,gBAAgB;CAAC;CAAS;CAAY;CAAa;AAAQ;AAIxE,MAAa,oBAAoB;CAAC;CAAU;CAAc;CAAM;CAAM;CAAQ;CAAU;CAAS;AAAQ;AAEzG,MAAa,sBAAsB,CAAC,OAAO;AAE3C,MAAa,uBAAuB"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/** All lifecycle statuses a build can carry. */\nexport const BUILD_STATUSES = [\n \"pending\",\n \"capturing\",\n \"comparing\",\n \"reviewing\",\n \"approved\",\n \"rejected\",\n \"failed\",\n] as const;\n\n/** Lifecycle status of a build. */\nexport type BuildStatus = (typeof BUILD_STATUSES)[number];\n\n/** All review statuses a snapshot can carry. */\nexport const SNAPSHOT_STATUSES = [\n \"pending\",\n \"new\",\n \"unchanged\",\n \"changed\",\n \"approved\",\n \"rejected\",\n] as const;\n\n/** Review status of a snapshot. */\nexport type SnapshotStatus = (typeof SNAPSHOT_STATUSES)[number];\n\n/** Build statuses eligible for retention purging. */\nexport const TERMINAL_BUILD_STATUSES: readonly BuildStatus[] = [\"approved\", \"rejected\", \"failed\"];\n\n/** All site-wide user roles. */\nexport const SITE_ROLES = [\"admin\", \"member\"] as const;\n\n/** Site-wide role of a user. */\nexport type SiteRole = (typeof SITE_ROLES)[number];\n\n/** All per-project membership roles. */\nexport const PROJECT_ROLES = [\"admin\", \"approver\", \"developer\", \"viewer\"] as const;\n\n/** Per-project role of a member. */\nexport type ProjectRole = (typeof PROJECT_ROLES)[number];\n\n/** Label type keys seeded for every new project. */\nexport const SEEDED_LABEL_KEYS = [\n \"branch\",\n \"persistent\",\n \"pr\",\n \"mr\",\n \"jira\",\n \"linear\",\n \"figma\",\n \"custom\",\n] as const;\n\n/** Label type keys reserved for internal use. */\nexport const RESERVED_LABEL_KEYS = [\"build\"] as const;\n\n/** Label key marking a build as exempt from retention purging. */\nexport const PERSISTENT_LABEL_KEY = \"persistent\";\n"],"mappings":";;;;;AACA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAMA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;AACF;;AAMA,MAAa,0BAAkD;CAAC;CAAY;CAAY;AAAQ;;AAGhG,MAAa,aAAa,CAAC,SAAS,QAAQ;;AAM5C,MAAa,gBAAgB;CAAC;CAAS;CAAY;CAAa;AAAQ;;AAMxE,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,sBAAsB,CAAC,OAAO;;AAG3C,MAAa,uBAAuB"}
@@ -6,6 +6,7 @@ import { baseStyle } from "./styles.mjs";
6
6
  import { DARK_THEME, LIGHT_THEME } from "./theme.mjs";
7
7
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
8
8
  //#region src/ui/document.tsx
9
+ /** Full HTML document shell with top bar, sidebar, theme, and HTMX. */
9
10
  const DocumentLayout = ({ title, nav, children }) => {
10
11
  const { ui } = getStore();
11
12
  const name = ui.name ?? "StoryShelf";
@@ -1 +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 === \"labels\" ? \"sidebar__link--active\" : \"\"}`} href={`/projects/${nav.projectSlug}/labels`} aria-current={nav.active === \"labels\" ? \"page\" : undefined}>\n <span aria-hidden=\"true\">⌗</span> Labels\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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"}
1
+ {"version":3,"file":"document.mjs","names":[],"sources":["../../src/ui/document.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { getStore } from \"../store.ts\";\nimport { baseStyle } from \"./styles.ts\";\nimport { DARK_THEME, LIGHT_THEME } from \"./theme.ts\";\n\n/** Rendered HTML content returned by page components. */\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/** Full HTML document shell with top bar, sidebar, theme, and HTMX. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const DocumentLayout: FC<{ title: string; nav?: NavConfig; children?: unknown }> = ({\n title,\n nav,\n children,\n}) => {\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\n class=\"topbar__menu\"\n type=\"button\"\n aria-label=\"Toggle navigation\"\n aria-expanded=\"false\"\n aria-controls=\"sidebar\"\n data-sidebar-toggle\n >\n <span aria-hidden=\"true\">☰</span>\n </button>\n <a class=\"brand\" href=\"/\">\n {logo ? (\n <img class=\"logo\" src={logo} alt={name} width=\"28\" height=\"28\" />\n ) : (\n <span class=\"brand__mark\" aria-hidden=\"true\">\n ◆\n </span>\n )}\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`}>\n {nav.projectName ?? nav.projectSlug}\n </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\n class={`sidebar__link ${nav?.active === \"projects\" ? \"sidebar__link--active\" : \"\"}`}\n href=\"/projects\"\n aria-current={nav?.active === \"projects\" ? \"page\" : undefined}\n >\n <span aria-hidden=\"true\">▦</span> Projects\n </a>\n {nav?.projectSlug ? (\n <>\n <div class=\"sidebar__section\">Project</div>\n <a\n class={`sidebar__link ${nav.active === \"builds\" ? \"sidebar__link--active\" : \"\"}`}\n href={`/projects/${nav.projectSlug}/builds`}\n aria-current={nav.active === \"builds\" ? \"page\" : undefined}\n >\n <span aria-hidden=\"true\">◧</span> Builds\n </a>\n <a\n class={`sidebar__link ${nav.active === \"jobs\" ? \"sidebar__link--active\" : \"\"}`}\n href={`/projects/${nav.projectSlug}/jobs`}\n aria-current={nav.active === \"jobs\" ? \"page\" : undefined}\n >\n <span aria-hidden=\"true\">◫</span> Jobs\n </a>\n <a\n class={`sidebar__link ${nav.active === \"labels\" ? \"sidebar__link--active\" : \"\"}`}\n href={`/projects/${nav.projectSlug}/labels`}\n aria-current={nav.active === \"labels\" ? \"page\" : undefined}\n >\n <span aria-hidden=\"true\">⌗</span> Labels\n </a>\n <a\n class={`sidebar__link ${nav.active === \"settings\" ? \"sidebar__link--active\" : \"\"}`}\n href={`/projects/${nav.projectSlug}/settings`}\n aria-current={nav.active === \"settings\" ? \"page\" : undefined}\n >\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,24 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { jsx } from "hono/jsx/jsx-runtime";
5
+ //#region src/ui/feedback.tsx
6
+ /** Small status pill with a color tone. */
7
+ const Badge = ({ tone = "neutral", children }) => {
8
+ return /* @__PURE__ */ jsx("span", {
9
+ class: `badge badge--${tone}`,
10
+ children
11
+ });
12
+ };
13
+ /** Map a build or snapshot status string to its badge tone. */
14
+ function statusTone(status) {
15
+ if (status === "approved" || status === "unchanged") return "success";
16
+ if (status === "rejected" || status === "failed") return "danger";
17
+ if (status === "changed" || status === "new" || status === "reviewing") return "warning";
18
+ if (status === "capturing" || status === "comparing" || status === "pending") return "info";
19
+ return "neutral";
20
+ }
21
+ //#endregion
22
+ export { Badge, statusTone };
23
+
24
+ //# sourceMappingURL=feedback.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback.mjs","names":[],"sources":["../../src/ui/feedback.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype BadgeTone = \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\n/** Small status pill with a color tone. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Badge: FC<{ tone?: BadgeTone; children?: unknown }> = ({\n tone = \"neutral\",\n children,\n}) => {\n return <span class={`badge badge--${tone}`}>{children}</span>;\n};\n\n/** Map a build or snapshot status string to its badge tone. */\nexport function statusTone(status: string): BadgeTone {\n if (status === \"approved\" || status === \"unchanged\") {\n return \"success\";\n }\n if (status === \"rejected\" || status === \"failed\") {\n return \"danger\";\n }\n if (status === \"changed\" || status === \"new\" || status === \"reviewing\") {\n return \"warning\";\n }\n if (status === \"capturing\" || status === \"comparing\" || status === \"pending\") {\n return \"info\";\n }\n return \"neutral\";\n}\n\n/** Alert banner with an optional title. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Alert: FC<{ tone?: BadgeTone; title?: string; children?: unknown }> = ({\n tone = \"info\",\n title,\n children,\n}) => {\n return (\n <div class={`alert alert--${tone}`} role=\"alert\">\n {title ? <strong class=\"alert__title\">{title}</strong> : null}\n <div class=\"alert__body\">{children}</div>\n </div>\n );\n};\n\n/** Centered empty state with an optional call-to-action. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const EmptyState: FC<{ title: string; description?: string; action?: unknown }> = ({\n title,\n description,\n action,\n}) => {\n return (\n <div class=\"empty\">\n <h2 class=\"empty__title\">{title}</h2>\n {description ? <p class=\"empty__desc\">{description}</p> : null}\n {action ? <div class=\"empty__action\">{action}</div> : null}\n </div>\n );\n};\n\n/** Centered statistic value with a label. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Stat: FC<{ label: string; value: string | number }> = ({ label, value }) => {\n return (\n <div class=\"stat\">\n <div class=\"stat__value\">{String(value)}</div>\n <div class=\"stat__label\">{label}</div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAQA,MAAa,SAAuD,EAClE,OAAO,WACP,eACI;CACJ,OAAO,oBAAC,QAAD;EAAM,OAAO,gBAAgB;EAAS;CAAe,CAAA;AAC9D;;AAGA,SAAgB,WAAW,QAA2B;CACpD,IAAI,WAAW,cAAc,WAAW,aACtC,OAAO;CAET,IAAI,WAAW,cAAc,WAAW,UACtC,OAAO;CAET,IAAI,WAAW,aAAa,WAAW,SAAS,WAAW,aACzD,OAAO;CAET,IAAI,WAAW,eAAe,WAAW,eAAe,WAAW,WACjE,OAAO;CAET,OAAO;AACT"}
@@ -2,20 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
5
- //#region src/ui/components.tsx
6
- const Badge = ({ tone = "neutral", children }) => {
7
- return /* @__PURE__ */ jsx("span", {
8
- class: `badge badge--${tone}`,
9
- children
10
- });
11
- };
12
- function statusTone(status) {
13
- if (status === "approved" || status === "unchanged") return "success";
14
- if (status === "rejected" || status === "failed") return "danger";
15
- if (status === "changed" || status === "new" || status === "reviewing") return "warning";
16
- if (status === "capturing" || status === "comparing" || status === "pending") return "info";
17
- return "neutral";
18
- }
5
+ //#region src/ui/forms.tsx
19
6
  const FieldAssistant = ({ name, error, hint }) => {
20
7
  if (error) return /* @__PURE__ */ jsx("p", {
21
8
  class: "field__error",
@@ -34,6 +21,7 @@ function fieldDescribedBy(name, error, hint) {
34
21
  if (error) return `${name}-error`;
35
22
  if (hint) return `${name}-hint`;
36
23
  }
24
+ /** Labeled text input with error and hint states. */
37
25
  const Field = ({ label, name, type = "text", value, placeholder, required, error, hint, autocomplete }) => {
38
26
  return /* @__PURE__ */ jsxs("div", {
39
27
  class: "field",
@@ -66,6 +54,7 @@ const Field = ({ label, name, type = "text", value, placeholder, required, error
66
54
  ]
67
55
  });
68
56
  };
57
+ /** Labeled textarea with error and hint states. */
69
58
  const TextareaField = ({ label, name, value, placeholder, rows = 3, hint, error }) => {
70
59
  return /* @__PURE__ */ jsxs("div", {
71
60
  class: "field",
@@ -93,6 +82,7 @@ const TextareaField = ({ label, name, value, placeholder, rows = 3, hint, error
93
82
  ]
94
83
  });
95
84
  };
85
+ /** Labeled select dropdown with an optional hint. */
96
86
  const SelectField = ({ label, name, value, options, hint }) => {
97
87
  return /* @__PURE__ */ jsxs("div", {
98
88
  class: "field",
@@ -122,6 +112,6 @@ const SelectField = ({ label, name, value, options, hint }) => {
122
112
  });
123
113
  };
124
114
  //#endregion
125
- export { Badge, Field, SelectField, TextareaField, statusTone };
115
+ export { Field, SelectField, TextareaField };
126
116
 
127
- //# sourceMappingURL=components.mjs.map
117
+ //# sourceMappingURL=forms.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forms.mjs","names":[],"sources":["../../src/ui/forms.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n// eslint-disable-next-line promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString>\nconst FieldAssistant: FC<{ name: string; error: string | undefined; hint: string | undefined }> = ({\n name,\n error,\n hint,\n}) => {\n if (error) {\n return (\n <p class=\"field__error\" id={`${name}-error`} role=\"alert\">\n {error}\n </p>\n );\n }\n if (hint) {\n return (\n <p class=\"field__hint\" id={`${name}-hint`}>\n {hint}\n </p>\n );\n }\n return null;\n};\n\nfunction fieldDescribedBy(\n name: string,\n error: string | undefined,\n hint: string | undefined,\n): string | undefined {\n if (error) {\n return `${name}-error`;\n }\n if (hint) {\n return `${name}-hint`;\n }\n return undefined;\n}\n\n/** Labeled text input with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Field: FC<{\n label: string;\n name: string;\n type?: string;\n value?: string;\n placeholder?: string;\n required?: boolean;\n error?: string;\n hint?: string;\n autocomplete?: string;\n}> = ({ label, name, type = \"text\", value, placeholder, required, error, hint, autocomplete }) => {\n return (\n <div class=\"field\">\n <label class=\"field__label\" for={name}>\n {label}\n {required ? <span aria-hidden=\"true\"> *</span> : null}\n </label>\n <input\n class={`field__input ${error ? \"field__input--error\" : \"\"}`}\n id={name}\n name={name}\n type={type}\n value={value}\n placeholder={placeholder}\n required={required}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n autocomplete={autocomplete}\n />\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\n/** Labeled textarea with error and hint states. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const TextareaField: FC<{\n label: string;\n name: string;\n value?: string;\n placeholder?: string;\n rows?: number;\n hint?: string;\n error?: string;\n}> = ({ label, name, value, placeholder, rows = 3, hint, error }) => {\n return (\n <div class=\"field\">\n <label class=\"field__label\" for={name}>\n {label}\n </label>\n <textarea\n class={`field__input field__input--textarea ${error ? \"field__input--error\" : \"\"}`}\n id={name}\n name={name}\n placeholder={placeholder}\n rows={rows}\n aria-invalid={error ? \"true\" : undefined}\n aria-describedby={fieldDescribedBy(name, error, hint)}\n >\n {value}\n </textarea>\n <FieldAssistant name={name} error={error} hint={hint} />\n </div>\n );\n};\n\n/** Labeled select dropdown with an optional hint. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const SelectField: FC<{\n label: string;\n name: string;\n value?: string;\n options: { value: string; label: string }[];\n hint?: string;\n}> = ({ label, name, value, options, hint }) => {\n return (\n <div class=\"field\">\n <label class=\"field__label\" for={name}>\n {label}\n </label>\n <select\n class=\"field__input\"\n id={name}\n name={name}\n aria-describedby={hint ? `${name}-hint` : undefined}\n >\n {options.map((opt) => (\n <option value={opt.value} selected={opt.value === value}>\n {opt.label}\n </option>\n ))}\n </select>\n {hint ? (\n <p class=\"field__hint\" id={`${name}-hint`}>\n {hint}\n </p>\n ) : null}\n </div>\n );\n};\n"],"mappings":";;;;;AAKA,MAAM,kBAA6F,EACjG,MACA,OACA,WACI;CACJ,IAAI,OACF,OACE,oBAAC,KAAD;EAAG,OAAM;EAAe,IAAI,GAAG,KAAK;EAAS,MAAK;EAC/C,UAAA;CACA,CAAA;CAGP,IAAI,MACF,OACE,oBAAC,KAAD;EAAG,OAAM;EAAc,IAAI,GAAG,KAAK;EAChC,UAAA;CACA,CAAA;CAGP,OAAO;AACT;AAEA,SAAS,iBACP,MACA,OACA,MACoB;CACpB,IAAI,OACF,OAAO,GAAG,KAAK;CAEjB,IAAI,MACF,OAAO,GAAG,KAAK;AAGnB;;AAIA,MAAa,SAUP,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,aAAa,UAAU,OAAO,MAAM,mBAAmB;CAChG,OACE,qBAAC,OAAD;EAAK,OAAM;EAAX,UAAA;GACE,qBAAC,SAAD;IAAO,OAAM;IAAe,KAAK;IAAjC,UAAA,CACG,OACA,WAAW,oBAAC,QAAD;KAAM,eAAY;KAAO,UAAA;IAAQ,CAAA,IAAI,IAC5C;;GACP,oBAAC,SAAD;IACE,OAAO,gBAAgB,QAAQ,wBAAwB;IACvD,IAAI;IACE;IACA;IACC;IACM;IACH;IACV,gBAAc,QAAQ,SAAS,KAAA;IAC/B,oBAAkB,iBAAiB,MAAM,OAAO,IAAI;IACtC;GACf,CAAA;GACD,oBAAC,gBAAD;IAAsB;IAAa;IAAa;GAAO,CAAA;EACpD;;AAET;;AAIA,MAAa,iBAQP,EAAE,OAAO,MAAM,OAAO,aAAa,OAAO,GAAG,MAAM,YAAY;CACnE,OACE,qBAAC,OAAD;EAAK,OAAM;EAAX,UAAA;GACE,oBAAC,SAAD;IAAO,OAAM;IAAe,KAAK;IAC9B,UAAA;GACI,CAAA;GACP,oBAAC,YAAD;IACE,OAAO,uCAAuC,QAAQ,wBAAwB;IAC9E,IAAI;IACE;IACO;IACP;IACN,gBAAc,QAAQ,SAAS,KAAA;IAC/B,oBAAkB,iBAAiB,MAAM,OAAO,IAAI;IAEnD,UAAA;GACO,CAAA;GACV,oBAAC,gBAAD;IAAsB;IAAa;IAAa;GAAO,CAAA;EACpD;;AAET;;AAIA,MAAa,eAMP,EAAE,OAAO,MAAM,OAAO,SAAS,WAAW;CAC9C,OACE,qBAAC,OAAD;EAAK,OAAM;EAAX,UAAA;GACE,oBAAC,SAAD;IAAO,OAAM;IAAe,KAAK;IAC9B,UAAA;GACI,CAAA;GACP,oBAAC,UAAD;IACE,OAAM;IACN,IAAI;IACE;IACN,oBAAkB,OAAO,GAAG,KAAK,SAAS,KAAA;IAEzC,UAAA,QAAQ,KAAK,QACZ,oBAAC,UAAD;KAAQ,OAAO,IAAI;KAAO,UAAU,IAAI,UAAU;KAC/C,UAAA,IAAI;IACC,CAAA,CACT;GACK,CAAA;GACP,OACC,oBAAC,KAAD;IAAG,OAAM;IAAc,IAAI,GAAG,KAAK;IAChC,UAAA;GACA,CAAA,IACD;EACD;;AAET"}
@@ -2,6 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  //#region src/ui/styles.ts
5
+ /** Build the global CSS stylesheet from the light and dark brand themes. */
5
6
  function baseStyle(light, dark) {
6
7
  return `
7
8
  :root {