@storyshelf/core 0.1.0 → 0.1.2

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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -8
  3. package/dist/adapter/capture-runner.d.mts +4 -0
  4. package/dist/adapter/git-host/helpers.mjs +1 -1
  5. package/dist/adapter/git-host/helpers.mjs.map +1 -1
  6. package/dist/adapter/storage.d.mts +1 -0
  7. package/dist/adapters/webhook-events.mjs.map +1 -1
  8. package/dist/capture/adapter.d.mts +9 -1
  9. package/dist/capture/adapter.mjs +16 -1
  10. package/dist/capture/adapter.mjs.map +1 -1
  11. package/dist/capture/orchestrator.mjs +37 -10
  12. package/dist/capture/orchestrator.mjs.map +1 -1
  13. package/dist/capture/pipeline.d.mts +5 -3
  14. package/dist/capture/pipeline.mjs +18 -6
  15. package/dist/capture/pipeline.mjs.map +1 -1
  16. package/dist/capture/queue.mjs +1 -1
  17. package/dist/capture/queue.mjs.map +1 -1
  18. package/dist/capture/storybook.mjs +11 -3
  19. package/dist/capture/storybook.mjs.map +1 -1
  20. package/dist/config.mjs +2 -2
  21. package/dist/config.mjs.map +1 -1
  22. package/dist/ddl.d.mts +1 -1
  23. package/dist/ddl.mjs +2 -0
  24. package/dist/ddl.mjs.map +1 -1
  25. package/dist/diff/engine.mjs +2 -2
  26. package/dist/diff/engine.mjs.map +1 -1
  27. package/dist/index.d.mts +4 -4
  28. package/dist/index.mjs +4 -2
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/logger.d.mts +3 -3
  31. package/dist/logger.mjs.map +1 -1
  32. package/dist/middleware/csrf.mjs +1 -1
  33. package/dist/middleware/csrf.mjs.map +1 -1
  34. package/dist/models/build.d.mts +16 -1
  35. package/dist/models/build.mjs +19 -1
  36. package/dist/models/build.mjs.map +1 -1
  37. package/dist/models/label.d.mts +6 -0
  38. package/dist/models/label.mjs +11 -0
  39. package/dist/models/label.mjs.map +1 -1
  40. package/dist/models/project.mjs +1 -1
  41. package/dist/models/project.mjs.map +1 -1
  42. package/dist/pages/build-detail.mjs +1 -1
  43. package/dist/pages/build-detail.mjs.map +1 -1
  44. package/dist/pages/build-diff.mjs.map +1 -1
  45. package/dist/pages/compute-jobs.mjs.map +1 -1
  46. package/dist/pages/label-detail.mjs +227 -0
  47. package/dist/pages/label-detail.mjs.map +1 -0
  48. package/dist/pages/project-builds.mjs.map +1 -1
  49. package/dist/pages/project-settings.mjs +32 -42
  50. package/dist/pages/project-settings.mjs.map +1 -1
  51. package/dist/pages/projects.mjs.map +1 -1
  52. package/dist/pages/settings-labels.mjs.map +1 -1
  53. package/dist/pages/settings-members.mjs.map +1 -1
  54. package/dist/pages/settings-tests.mjs +148 -0
  55. package/dist/pages/settings-tests.mjs.map +1 -0
  56. package/dist/pages/settings-tokens.mjs.map +1 -1
  57. package/dist/pages/settings-webhooks.mjs.map +1 -1
  58. package/dist/pages/storybook.mjs +59 -0
  59. package/dist/pages/storybook.mjs.map +1 -0
  60. package/dist/routers/builds.handlers.mjs +5 -3
  61. package/dist/routers/builds.handlers.mjs.map +1 -1
  62. package/dist/routers/builds.mjs +4 -2
  63. package/dist/routers/builds.mjs.map +1 -1
  64. package/dist/routers/helpers.mjs.map +1 -1
  65. package/dist/routers/labels.mjs +33 -1
  66. package/dist/routers/labels.mjs.map +1 -1
  67. package/dist/routers/schemas.mjs +15 -4
  68. package/dist/routers/schemas.mjs.map +1 -1
  69. package/dist/routers/settings.mjs +20 -0
  70. package/dist/routers/settings.mjs.map +1 -1
  71. package/dist/routers/storybook.mjs +112 -0
  72. package/dist/routers/storybook.mjs.map +1 -0
  73. package/dist/routers/ui.mjs +24 -2
  74. package/dist/routers/ui.mjs.map +1 -1
  75. package/dist/schema.d.mts +68 -0
  76. package/dist/schema.mjs +2 -0
  77. package/dist/schema.mjs.map +1 -1
  78. package/dist/types.d.mts +2 -1
  79. package/dist/types.mjs.map +1 -1
  80. package/dist/ui/components.mjs.map +1 -1
  81. package/dist/ui/document.mjs +9 -0
  82. package/dist/ui/document.mjs.map +1 -1
  83. package/dist/utils/ulid.mjs +3 -3
  84. package/dist/utils/ulid.mjs.map +1 -1
  85. package/package.json +76 -67
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/routers/storybook.ts"],"sourcesContent":["import { posix } from \"node:path\";\n\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { isPublicBuild, BuildModel } from \"../models/build.ts\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { renderStorybookPage } from \"../pages/storybook.tsx\";\nimport { getStore } from \"../store.ts\";\nimport type { Build, Project } from \"../schema.ts\";\nimport { storybookDir } from \"../utils/paths.ts\";\nimport { currentProjectRole, notFound } from \"./helpers.ts\";\n\nconst VIEW_ROLES: ReadonlySet<string> = new Set([\"viewer\", \"developer\", \"approver\", \"admin\"]);\n\nconst MIME: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".htm\": \"text/html; charset=utf-8\",\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".json\": \"application/json\",\n \".css\": \"text/css; charset=utf-8\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".svg\": \"image/svg+xml\",\n \".webp\": \"image/webp\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".map\": \"application/json\",\n \".wasm\": \"application/wasm\",\n};\n\nfunction contentTypeFor(path: string): string {\n const ext = posix.extname(path).toLowerCase();\n return MIME[ext] ?? \"application/octet-stream\";\n}\n\nfunction isSafeSegment(segment: string): boolean {\n return segment !== \"..\" && !segment.startsWith(\"/\") && !segment.includes(\"\\\\\") && !segment.includes(\"..\");\n}\n\n/**\n * Decide whether the current request may view `build`'s published Storybook.\n *\n * Public builds (ADR 0011) are viewable without auth; every other build requires\n * a logged-in session with at least `viewer` membership on the project.\n */\nasync function canViewBuild(build: Pick<Build, \"public\" | \"gitBranch\">, project: Project): Promise<boolean> {\n if (isPublicBuild(project, build)) {\n return true;\n }\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const role = await currentProjectRole(project.id);\n return Boolean(role && VIEW_ROLES.has(role));\n}\n\nexport function registerStorybook(app: ShelfApp): void {\n // Resolver: latest published build on the default branch.\n app.get(\"/projects/:slug/storybook\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const build = await new BuildModel(getStore().db).latestPublished(project);\n if (!build) {\n notFound(\"No published Storybook for this project\");\n }\n if (!(await canViewBuild(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n return c.redirect(`/projects/${slug}/storybook/build/${build.id}/`, 302);\n });\n\n // Resolver: latest build bearing a label (`build` is a reserved label key).\n app.get(\"/projects/:slug/storybook/:key/:value\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n if (key === \"build\") {\n return c.notFound();\n }\n const value = c.req.param(\"value\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const buildId = await new LabelModel(getStore().db).latestBuildId(project.id, key, value);\n if (!buildId) {\n notFound(\"No build carries that label\");\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || !(await canViewBuild(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n return c.redirect(`/projects/${slug}/storybook/build/${buildId}/`, 302);\n });\n\n // Canonical build route: serves the static Storybook and the landing page.\n app.get(\"/projects/:slug/storybook/build/:buildId/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // remainder from the raw request path (un-encoded) and decode it ourselves.\n const base = `/projects/${slug}/storybook/build/${buildId}/`;\n if (!c.req.path.startsWith(base)) {\n return c.notFound();\n }\n let rest: string;\n try {\n rest = decodeURIComponent(c.req.path.slice(base.length));\n } catch {\n return c.notFound();\n }\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n notFound(\"Build not found\");\n }\n if (!(await canViewBuild(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n\n if (rest === \"\") {\n return c.html(renderStorybookPage(project, build, slug), 200);\n }\n\n const segments = rest.split(\"/\");\n if (segments.some((segment) => !isSafeSegment(segment))) {\n return c.notFound();\n }\n const path = posix.join(storybookDir(project.id, build.id), rest);\n if (!(await getStore().storage.exists(path))) {\n return c.notFound();\n }\n const buffer = await getStore().storage.read(path);\n return c.body(new Uint8Array(buffer), 200, {\n \"content-type\": contentTypeFor(rest),\n \"cache-control\": \"public, max-age=3600\",\n });\n });\n}\n"],"mappings":"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"}
@@ -13,6 +13,7 @@ import { CommentModel } from "../models/comment.mjs";
13
13
  import { renderBuildDetailPage } from "../pages/build-detail.mjs";
14
14
  import { renderBuildDiffPage } from "../pages/build-diff.mjs";
15
15
  import { renderActiveQueue, renderComputeJobsPage } from "../pages/compute-jobs.mjs";
16
+ import { renderLabelDetailPage, renderLabelsPage } from "../pages/label-detail.mjs";
16
17
  import { renderProjectCreatePage } from "../pages/project-create.mjs";
17
18
  import { renderProjectBuildsPage } from "../pages/project-builds.mjs";
18
19
  import { renderProjectsPage } from "../pages/projects.mjs";
@@ -22,6 +23,13 @@ import { registerSettingsPages } from "./settings.mjs";
22
23
  function asString(value) {
23
24
  return typeof value === "string" ? value.trim() : void 0;
24
25
  }
26
+ function decodeRest(value) {
27
+ try {
28
+ return decodeURIComponent(value);
29
+ } catch {
30
+ return "";
31
+ }
32
+ }
25
33
  async function getQueueView() {
26
34
  const queue = getStore().captureQueue;
27
35
  if (!queue) return [];
@@ -46,9 +54,9 @@ async function canManageJobs(projectId) {
46
54
  return Boolean(role && roles.includes(role));
47
55
  }
48
56
  function registerUiPages(app) {
49
- app.get("/", (c) => c.html(renderRootPage()));
57
+ app.get("/", async (c) => c.html(await renderRootPage()));
50
58
  app.get("/projects", async (c) => c.html(await renderProjectsPage()));
51
- app.get("/projects/new", (c) => c.html(renderProjectCreatePage()));
59
+ app.get("/projects/new", async (c) => c.html(await renderProjectCreatePage()));
52
60
  app.post("/projects/new", async (c) => {
53
61
  const form = await c.req.formData();
54
62
  const name = asString(form.get("name"));
@@ -134,6 +142,20 @@ function registerUiPages(app) {
134
142
  hasBaseline
135
143
  }));
136
144
  });
145
+ app.get("/projects/:slug/labels", async (c) => {
146
+ const html = await renderLabelsPage(c.req.param("slug"));
147
+ if (!html) return c.notFound();
148
+ return c.html(html);
149
+ });
150
+ app.get("/projects/:slug/labels/:key/*", async (c) => {
151
+ const slug = c.req.param("slug");
152
+ const key = c.req.param("key");
153
+ const base = `/projects/${slug}/labels/${key}/`;
154
+ const value = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : "";
155
+ const html = await renderLabelDetailPage(slug, key, value);
156
+ if (!html) return c.notFound();
157
+ return c.html(html);
158
+ });
137
159
  registerSettingsPages(app);
138
160
  }
139
161
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"ui.mjs","names":[],"sources":["../../src/routers/ui.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\n\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { CommentModel } from \"../models/comment.ts\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { renderBuildDetailPage } from \"../pages/build-detail.tsx\";\nimport { renderBuildDiffPage } from \"../pages/build-diff.tsx\";\nimport { renderComputeJobsPage, renderActiveQueue } from \"../pages/compute-jobs.tsx\";\nimport { renderProjectCreatePage } from \"../pages/project-create.tsx\";\nimport { renderProjectBuildsPage } from \"../pages/project-builds.tsx\";\nimport { renderProjectsPage } from \"../pages/projects.tsx\";\nimport { renderRootPage } from \"../pages/root.tsx\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { currentProjectRole } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { registerSettingsPages } from \"./settings.ts\";\n\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}\n\ninterface QueueView {\n buildId: string;\n status: string;\n queuedAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n}\n\nasync function getQueueView(): Promise<QueueView[]> {\n const queue = getStore().captureQueue;\n if (!queue) {\n return [];\n }\n const active = await queue.active();\n return active.map((entry) => ({\n buildId: entry.buildId,\n status: entry.status,\n queuedAt: entry.queuedAt,\n startedAt: entry.startedAt,\n finishedAt: entry.finishedAt,\n error: entry.error,\n }));\n}\n\nasync function canManageJobs(projectId: string): Promise<boolean> {\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const roles: readonly ProjectRole[] = [\"developer\", \"approver\", \"admin\"];\n const role = await currentProjectRole(projectId);\n return Boolean(role && roles.includes(role));\n}\n\nexport function registerUiPages(app: ShelfApp): void {\n // eslint-disable-next-line promise-function-async -- renderRootPage returns RenderedContent (string | Promise<string>)\n app.get(\"/\", (c) => c.html(renderRootPage()));\n app.get(\"/projects\", async (c) => c.html(await renderProjectsPage()));\n // eslint-disable-next-line promise-function-async -- renderProjectCreatePage returns RenderedContent (string | Promise<string>)\n app.get(\"/projects/new\", (c) => c.html(renderProjectCreatePage()));\n\n app.post(\"/projects/new\", async (c) => {\n const form = await c.req.formData();\n const name = asString(form.get(\"name\"));\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n if (!name) {\n return c.html(renderProjectCreatePage({ values: { name, gitRepository, gitDefaultBranch }, errors: { name: \"Name is required\" } }), 400);\n }\n try {\n const project = await new ProjectModel(getStore().db).create({ name, gitRepository, gitDefaultBranch });\n await new LabelModel(getStore().db).seedFor(project.id);\n return hxRedirect(c, `/projects/${project.slug}/builds`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create project\";\n return c.html(renderProjectCreatePage({ values: { name, gitRepository, gitDefaultBranch }, globalError: message }), 400);\n }\n });\n\n app.get(\"/projects/:slug/builds\", async (c) => {\n const html = await renderProjectBuildsPage(c.req.param(\"slug\"), {\n status: c.req.query(\"status\"),\n branch: c.req.query(\"branch\"),\n });\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId\", async (c) => {\n const html = await renderBuildDetailPage(c.req.param(\"buildId\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/jobs\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const queueView = await getQueueView();\n const canRetry = await canManageJobs(project.id);\n if (c.req.query(\"partial\") === \"queue\") {\n return c.html(renderActiveQueue(slug, queueView));\n }\n const html = await renderComputeJobsPage(slug, queueView, canRetry);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId/diff\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n const snapshotId = c.req.query(\"snapshot\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n return c.notFound();\n }\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const comments = await new CommentModel(getStore().db).listByBuild(build.id);\n const baselines = new BaselineModel(getStore().db, getStore().storage);\n const hasBaselineEntries = await Promise.all(\n snapshots.map(async (snapshot) => {\n const baseline = await baselines.resolve(project.id, snapshot.storyId, snapshot.viewportName, build.gitBranch, project.gitDefaultBranch);\n return [snapshot.id, Boolean(baseline)] as const;\n }),\n );\n const hasBaseline = Object.fromEntries(hasBaselineEntries);\n const { user, authEnabled } = getStore();\n const canReview = !authEnabled || Boolean(user);\n return c.html(\n renderBuildDiffPage({\n project,\n build,\n snapshots,\n comments,\n selectedId: snapshotId,\n canReview,\n hasBaseline,\n }),\n );\n });\n\n registerSettingsPages(app);\n}"],"mappings":"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"}
1
+ {"version":3,"file":"ui.mjs","names":[],"sources":["../../src/routers/ui.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\n\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { CommentModel } from \"../models/comment.ts\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { renderBuildDetailPage } from \"../pages/build-detail.tsx\";\nimport { renderBuildDiffPage } from \"../pages/build-diff.tsx\";\nimport { renderComputeJobsPage, renderActiveQueue } from \"../pages/compute-jobs.tsx\";\nimport { renderLabelDetailPage, renderLabelsPage } from \"../pages/label-detail.tsx\";\nimport { renderProjectCreatePage } from \"../pages/project-create.tsx\";\nimport { renderProjectBuildsPage } from \"../pages/project-builds.tsx\";\nimport { renderProjectsPage } from \"../pages/projects.tsx\";\nimport { renderRootPage } from \"../pages/root.tsx\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { currentProjectRole } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { registerSettingsPages } from \"./settings.ts\";\n\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}\n\nfunction decodeRest(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return \"\";\n }\n}\n\ninterface QueueView {\n buildId: string;\n status: string;\n queuedAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: string;\n}\n\nasync function getQueueView(): Promise<QueueView[]> {\n const queue = getStore().captureQueue;\n if (!queue) {\n return [];\n }\n const active = await queue.active();\n return active.map((entry) => ({\n buildId: entry.buildId,\n status: entry.status,\n queuedAt: entry.queuedAt,\n startedAt: entry.startedAt,\n finishedAt: entry.finishedAt,\n error: entry.error,\n }));\n}\n\nasync function canManageJobs(projectId: string): Promise<boolean> {\n const { authEnabled } = getStore();\n if (!authEnabled) {\n return true;\n }\n const roles: readonly ProjectRole[] = [\"developer\", \"approver\", \"admin\"];\n const role = await currentProjectRole(projectId);\n return Boolean(role && roles.includes(role));\n}\n\nexport function registerUiPages(app: ShelfApp): void {\n // eslint-disable-next-line promise-function-async -- renderRootPage returns RenderedContent (string | Promise<string>)\n app.get(\"/\", async (c) => c.html(await renderRootPage()));\n app.get(\"/projects\", async (c) => c.html(await renderProjectsPage()));\n // eslint-disable-next-line promise-function-async -- renderProjectCreatePage returns RenderedContent (string | Promise<string>)\n app.get(\"/projects/new\", async (c) => c.html(await renderProjectCreatePage()));\n\n app.post(\"/projects/new\", async (c) => {\n const form = await c.req.formData();\n const name = asString(form.get(\"name\"));\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n if (!name) {\n return c.html(renderProjectCreatePage({ values: { name, gitRepository, gitDefaultBranch }, errors: { name: \"Name is required\" } }), 400);\n }\n try {\n const project = await new ProjectModel(getStore().db).create({ name, gitRepository, gitDefaultBranch });\n await new LabelModel(getStore().db).seedFor(project.id);\n return hxRedirect(c, `/projects/${project.slug}/builds`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create project\";\n return c.html(renderProjectCreatePage({ values: { name, gitRepository, gitDefaultBranch }, globalError: message }), 400);\n }\n });\n\n app.get(\"/projects/:slug/builds\", async (c) => {\n const html = await renderProjectBuildsPage(c.req.param(\"slug\"), {\n status: c.req.query(\"status\"),\n branch: c.req.query(\"branch\"),\n });\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId\", async (c) => {\n const html = await renderBuildDetailPage(c.req.param(\"buildId\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/jobs\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const queueView = await getQueueView();\n const canRetry = await canManageJobs(project.id);\n if (c.req.query(\"partial\") === \"queue\") {\n return c.html(renderActiveQueue(slug, queueView));\n }\n const html = await renderComputeJobsPage(slug, queueView, canRetry);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/builds/:buildId/diff\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n const snapshotId = c.req.query(\"snapshot\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return c.notFound();\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n return c.notFound();\n }\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const comments = await new CommentModel(getStore().db).listByBuild(build.id);\n const baselines = new BaselineModel(getStore().db, getStore().storage);\n const hasBaselineEntries = await Promise.all(\n snapshots.map(async (snapshot) => {\n const baseline = await baselines.resolve(project.id, snapshot.storyId, snapshot.viewportName, build.gitBranch, project.gitDefaultBranch);\n return [snapshot.id, Boolean(baseline)] as const;\n }),\n );\n const hasBaseline = Object.fromEntries(hasBaselineEntries);\n const { user, authEnabled } = getStore();\n const canReview = !authEnabled || Boolean(user);\n return c.html(\n renderBuildDiffPage({\n project,\n build,\n snapshots,\n comments,\n selectedId: snapshotId,\n canReview,\n hasBaseline,\n }),\n );\n });\n\n app.get(\"/projects/:slug/labels\", async (c) => {\n const html = await renderLabelsPage(c.req.param(\"slug\"));\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n app.get(\"/projects/:slug/labels/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // label value from the raw request path and decode it ourselves.\n const base = `/projects/${slug}/labels/${key}/`;\n const value = c.req.path.startsWith(base) ? decodeRest(c.req.path.slice(base.length)) : \"\";\n const html = await renderLabelDetailPage(slug, key, value);\n if (!html) {\n return c.notFound();\n }\n return c.html(html);\n });\n\n registerSettingsPages(app);\n}"],"mappings":"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"}
package/dist/schema.d.mts CHANGED
@@ -184,6 +184,40 @@ declare const projects: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns
184
184
  length: number | undefined;
185
185
  $type: string | null | undefined;
186
186
  }>;
187
+ executePlay: import("drizzle-orm/sqlite-core").SQLiteColumn<{
188
+ name: "execute_play";
189
+ tableName: "projects";
190
+ dataType: "boolean";
191
+ columnType: "SQLiteBoolean";
192
+ data: boolean;
193
+ driverParam: number;
194
+ notNull: true;
195
+ hasDefault: true;
196
+ isPrimaryKey: false;
197
+ isAutoincrement: false;
198
+ hasRuntimeDefault: false;
199
+ enumValues: undefined;
200
+ baseColumn: never;
201
+ identity: undefined;
202
+ generated: undefined;
203
+ }, {}, {}>;
204
+ playTimeoutMs: import("drizzle-orm/sqlite-core").SQLiteColumn<{
205
+ name: "play_timeout_ms";
206
+ tableName: "projects";
207
+ dataType: "number";
208
+ columnType: "SQLiteInteger";
209
+ data: number;
210
+ driverParam: number;
211
+ notNull: true;
212
+ hasDefault: true;
213
+ isPrimaryKey: false;
214
+ isAutoincrement: false;
215
+ hasRuntimeDefault: false;
216
+ enumValues: undefined;
217
+ baseColumn: never;
218
+ identity: undefined;
219
+ generated: undefined;
220
+ }, {}, {}>;
187
221
  createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
188
222
  name: "created_at";
189
223
  tableName: "projects";
@@ -2355,6 +2389,40 @@ declare const schema: {
2355
2389
  length: number | undefined;
2356
2390
  $type: string | null | undefined;
2357
2391
  }>;
2392
+ executePlay: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2393
+ name: "execute_play";
2394
+ tableName: "projects";
2395
+ dataType: "boolean";
2396
+ columnType: "SQLiteBoolean";
2397
+ data: boolean;
2398
+ driverParam: number;
2399
+ notNull: true;
2400
+ hasDefault: true;
2401
+ isPrimaryKey: false;
2402
+ isAutoincrement: false;
2403
+ hasRuntimeDefault: false;
2404
+ enumValues: undefined;
2405
+ baseColumn: never;
2406
+ identity: undefined;
2407
+ generated: undefined;
2408
+ }, {}, {}>;
2409
+ playTimeoutMs: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2410
+ name: "play_timeout_ms";
2411
+ tableName: "projects";
2412
+ dataType: "number";
2413
+ columnType: "SQLiteInteger";
2414
+ data: number;
2415
+ driverParam: number;
2416
+ notNull: true;
2417
+ hasDefault: true;
2418
+ isPrimaryKey: false;
2419
+ isAutoincrement: false;
2420
+ hasRuntimeDefault: false;
2421
+ enumValues: undefined;
2422
+ baseColumn: never;
2423
+ identity: undefined;
2424
+ generated: undefined;
2425
+ }, {}, {}>;
2358
2426
  createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2359
2427
  name: "created_at";
2360
2428
  tableName: "projects";
package/dist/schema.mjs CHANGED
@@ -13,6 +13,8 @@ const projects = sqliteTable("projects", {
13
13
  maxDiffRatio: real("max_diff_ratio").notNull().default(.01),
14
14
  publicBranchRegex: text("public_branch_regex"),
15
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),
16
18
  createdAt: text("created_at").notNull(),
17
19
  updatedAt: text("updated_at").notNull()
18
20
  });
@@ -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 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":[],"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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"}
package/dist/types.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
+ import { Logger } from "./logger.mjs";
3
4
  //#region src/types.d.ts
4
5
  declare const BUILD_STATUSES: readonly ["pending", "capturing", "comparing", "reviewing", "approved", "rejected", "failed"];
5
6
  type BuildStatus = (typeof BUILD_STATUSES)[number];
@@ -14,5 +15,5 @@ declare const SEEDED_LABEL_KEYS: readonly ["branch", "persistent", "pr", "mr", "
14
15
  declare const RESERVED_LABEL_KEYS: readonly ["build"];
15
16
  declare const PERSISTENT_LABEL_KEY = "persistent";
16
17
  //#endregion
17
- 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 };
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 };
18
19
  //# sourceMappingURL=types.d.mts.map
@@ -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"],"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":["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 +1 @@
1
- {"version":3,"file":"components.mjs","names":[],"sources":["../../src/ui/components.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\";\ntype BadgeTone = \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nexport const Button: FC<{\n variant?: ButtonVariant;\n type?: \"button\" | \"submit\" | \"reset\";\n href?: string;\n disabled?: boolean;\n children?: unknown;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}> = ({ variant = \"primary\", type = \"button\", href, disabled, children, ...rest }) => {\n const className = `btn btn--${variant}`;\n if (href) {\n return (\n <a class={className} href={href} aria-disabled={disabled ? \"true\" : undefined} {...rest}>\n {children}\n </a>\n );\n }\n return (\n <button class={className} type={type} disabled={disabled} {...rest}>\n {children}\n </button>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Badge: FC<{ tone?: BadgeTone; children?: unknown }> = ({ tone = \"neutral\", children }) => {\n return <span class={`badge badge--${tone}`}>{children}</span>;\n};\n\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// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Card: FC<{ children?: unknown; padded?: boolean }> = ({ children, padded = true }) => {\n return <div class={padded ? \"card card--padded\" : \"card\"}>{children}</div>;\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Alert: FC<{ tone?: BadgeTone; title?: string; children?: unknown }> = ({ tone = \"info\", title, children }) => {\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// 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 }> = ({ name, error, hint }) => {\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(name: string, error: string | undefined, hint: string | undefined): string | undefined {\n if (error) {\n return `${name}-error`;\n }\n if (hint) {\n return `${name}-hint`;\n }\n return undefined;\n}\n\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// 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// 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 class=\"field__input\" id={name} name={name} aria-describedby={hint ? `${name}-hint` : undefined}>\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\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const PageHeader: FC<{\n title: string;\n description?: string;\n actions?: unknown;\n breadcrumbs?: { label: string; href?: string }[];\n}> = ({ title, description, actions, breadcrumbs }) => {\n return (\n <div class=\"page-header\">\n {breadcrumbs && breadcrumbs.length > 0 ? (\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n {breadcrumbs.map((crumb, index) => (\n <li key={String(index)}>\n {crumb.href ? <a href={crumb.href}>{crumb.label}</a> : <span aria-current=\"page\">{crumb.label}</span>}\n </li>\n ))}\n </ol>\n </nav>\n ) : null}\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">{title}</h1>\n {description ? <p class=\"page-header__desc\">{description}</p> : null}\n </div>\n {actions ? <div class=\"page-header__actions\">{actions}</div> : null}\n </div>\n </div>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const EmptyState: FC<{ title: string; description?: string; action?: unknown }> = ({ title, description, action }) => {\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// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Tabs: FC<{\n tabs: { label: string; href: string; active?: boolean }[];\n}> = ({ tabs }) => {\n return (\n <nav class=\"tabs\" aria-label=\"Sections\">\n {tabs.map((tab) => (\n <a class={`tabs__link ${tab.active ? \"tabs__link--active\" : \"\"}`} href={tab.href} aria-current={tab.active ? \"page\" : undefined}>\n {tab.label}\n </a>\n ))}\n </nav>\n );\n};\n\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":";;;;;AAgCA,MAAa,SAAuD,EAAE,OAAO,WAAW,eAAe;CACrG,OAAO,oBAAC,QAAD;EAAM,OAAO,gBAAgB;EAAS;CAAe,CAAA;AAC9D;AAEA,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;AAkBA,MAAM,kBAA6F,EAAE,MAAM,OAAO,WAAW;CAC3H,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,iBAAiB,MAAc,OAA2B,MAA8C;CAC/G,IAAI,OACF,OAAO,GAAG,KAAK;CAEjB,IAAI,MACF,OAAO,GAAG,KAAK;AAGnB;AAGA,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;AAGA,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;AAGA,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;IAAQ,OAAM;IAAe,IAAI;IAAY;IAAM,oBAAkB,OAAO,GAAG,KAAK,SAAS,KAAA;IAC1F,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"}
1
+ {"version":3,"file":"components.mjs","names":[],"sources":["../../src/ui/components.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\";\ntype BadgeTone = \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\";\n\nexport const Button: FC<{\n variant?: ButtonVariant;\n type?: \"button\" | \"submit\" | \"reset\";\n href?: string;\n disabled?: boolean;\n children?: unknown;\n [key: string]: unknown;\n}> = ({ variant = \"primary\", type = \"button\", href, disabled, children, ...rest }) => {\n const className = `btn btn--${variant}`;\n if (href) {\n return (\n <a class={className} href={href} aria-disabled={disabled ? \"true\" : undefined} {...rest}>\n {children}\n </a>\n );\n }\n return (\n <button class={className} type={type} disabled={disabled} {...rest}>\n {children}\n </button>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Badge: FC<{ tone?: BadgeTone; children?: unknown }> = ({ tone = \"neutral\", children }) => {\n return <span class={`badge badge--${tone}`}>{children}</span>;\n};\n\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// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Card: FC<{ children?: unknown; padded?: boolean }> = ({ children, padded = true }) => {\n return <div class={padded ? \"card card--padded\" : \"card\"}>{children}</div>;\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Alert: FC<{ tone?: BadgeTone; title?: string; children?: unknown }> = ({ tone = \"info\", title, children }) => {\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// 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 }> = ({ name, error, hint }) => {\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(name: string, error: string | undefined, hint: string | undefined): string | undefined {\n if (error) {\n return `${name}-error`;\n }\n if (hint) {\n return `${name}-hint`;\n }\n return undefined;\n}\n\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// 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// 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 class=\"field__input\" id={name} name={name} aria-describedby={hint ? `${name}-hint` : undefined}>\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\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const PageHeader: FC<{\n title: string;\n description?: string;\n actions?: unknown;\n breadcrumbs?: { label: string; href?: string }[];\n}> = ({ title, description, actions, breadcrumbs }) => {\n return (\n <div class=\"page-header\">\n {breadcrumbs && breadcrumbs.length > 0 ? (\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n {breadcrumbs.map((crumb, index) => (\n <li key={String(index)}>\n {crumb.href ? <a href={crumb.href}>{crumb.label}</a> : <span aria-current=\"page\">{crumb.label}</span>}\n </li>\n ))}\n </ol>\n </nav>\n ) : null}\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">{title}</h1>\n {description ? <p class=\"page-header__desc\">{description}</p> : null}\n </div>\n {actions ? <div class=\"page-header__actions\">{actions}</div> : null}\n </div>\n </div>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const EmptyState: FC<{ title: string; description?: string; action?: unknown }> = ({ title, description, action }) => {\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// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Tabs: FC<{\n tabs: { label: string; href: string; active?: boolean }[];\n}> = ({ tabs }) => {\n return (\n <nav class=\"tabs\" aria-label=\"Sections\">\n {tabs.map((tab) => (\n <a class={`tabs__link ${tab.active ? \"tabs__link--active\" : \"\"}`} href={tab.href} aria-current={tab.active ? \"page\" : undefined}>\n {tab.label}\n </a>\n ))}\n </nav>\n );\n};\n\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":";;;;;AA+BA,MAAa,SAAuD,EAAE,OAAO,WAAW,eAAe;CACrG,OAAO,oBAAC,QAAD;EAAM,OAAO,gBAAgB;EAAS;CAAe,CAAA;AAC9D;AAEA,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;AAkBA,MAAM,kBAA6F,EAAE,MAAM,OAAO,WAAW;CAC3H,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,iBAAiB,MAAc,OAA2B,MAA8C;CAC/G,IAAI,OACF,OAAO,GAAG,KAAK;CAEjB,IAAI,MACF,OAAO,GAAG,KAAK;AAGnB;AAGA,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;AAGA,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;AAGA,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;IAAQ,OAAM;IAAe,IAAI;IAAY;IAAM,oBAAkB,OAAO,GAAG,KAAK,SAAS,KAAA;IAC1F,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"}
@@ -226,6 +226,15 @@ const Sidebar = ({ nav }) => {
226
226
  children: "◫"
227
227
  }), " Jobs"]
228
228
  }),
229
+ /* @__PURE__ */ jsxs("a", {
230
+ class: `sidebar__link ${nav.active === "labels" ? "sidebar__link--active" : ""}`,
231
+ href: `/projects/${nav.projectSlug}/labels`,
232
+ "aria-current": nav.active === "labels" ? "page" : void 0,
233
+ children: [/* @__PURE__ */ jsx("span", {
234
+ "aria-hidden": "true",
235
+ children: "⌗"
236
+ }), " Labels"]
237
+ }),
229
238
  /* @__PURE__ */ jsxs("a", {
230
239
  class: `sidebar__link ${nav.active === "settings" ? "sidebar__link--active" : ""}`,
231
240
  href: `/projects/${nav.projectSlug}/settings`,
@@ -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 === \"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\";\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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"}
@@ -9,7 +9,7 @@ function encodeTime(time) {
9
9
  let remaining = time;
10
10
  for (let i = 9; i >= 0; i -= 1) {
11
11
  const mod = remaining % 32;
12
- out = CROCKFORD[mod] + out;
12
+ out = CROCKFORD.charAt(mod) + out;
13
13
  remaining = (remaining - mod) / 32;
14
14
  }
15
15
  return out;
@@ -22,11 +22,11 @@ function encodeRandom(bytes) {
22
22
  buffer = buffer << 8 | byte;
23
23
  bits += 8;
24
24
  while (bits >= 5) {
25
- out += CROCKFORD[buffer >>> bits - 5 & 31];
25
+ out += CROCKFORD.charAt(buffer >>> bits - 5 & 31);
26
26
  bits -= 5;
27
27
  }
28
28
  }
29
- if (bits > 0) out += CROCKFORD[buffer << 5 - bits & 31];
29
+ if (bits > 0) out += CROCKFORD.charAt(buffer << 5 - bits & 31);
30
30
  return out;
31
31
  }
32
32
  function ulid() {
@@ -1 +1 @@
1
- {"version":3,"file":"ulid.mjs","names":[],"sources":["../../src/utils/ulid.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\n\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nfunction encodeTime(time: number): string {\n let out = \"\";\n let remaining = time;\n for (let i = 9; i >= 0; i -= 1) {\n const mod = remaining % 32;\n // eslint-disable-next-line no-non-null-assertion -- index is within CROCKFORD length\n out = CROCKFORD[mod]! + out;\n remaining = (remaining - mod) / 32;\n }\n return out;\n}\n\n/* eslint-disable no-bitwise, no-non-null-assertion -- ULID encoding is inherently bitwise. */\nfunction encodeRandom(bytes: Buffer): string {\n let out = \"\";\n let buffer = 0;\n let bits = 0;\n for (const byte of bytes) {\n buffer = (buffer << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n out += CROCKFORD[(buffer >>> (bits - 5)) & 31]!;\n bits -= 5;\n }\n }\n if (bits > 0) {\n out += CROCKFORD[(buffer << (5 - bits)) & 31]!;\n }\n return out;\n}\n/* eslint-enable no-bitwise, no-non-null-assertion */\n\nexport function ulid(): string {\n return encodeTime(Date.now()) + encodeRandom(randomBytes(10));\n}\n\nexport function slugify(value: string): string {\n return value\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/gu, \"-\")\n .replaceAll(/^-+|-+$/gu, \"\")\n .slice(0, 63);\n}\n"],"mappings":";;;;;AAEA,MAAM,YAAY;AAElB,SAAS,WAAW,MAAsB;CACxC,IAAI,MAAM;CACV,IAAI,YAAY;CAChB,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG;EAC9B,MAAM,MAAM,YAAY;EAExB,MAAM,UAAU,OAAQ;EACxB,aAAa,YAAY,OAAO;CAClC;CACA,OAAO;AACT;AAGA,SAAS,aAAa,OAAuB;CAC3C,IAAI,MAAM;CACV,IAAI,SAAS;CACb,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OAAO;EACxB,SAAU,UAAU,IAAK;EACzB,QAAQ;EACR,OAAO,QAAQ,GAAG;GAChB,OAAO,UAAW,WAAY,OAAO,IAAM;GAC3C,QAAQ;EACV;CACF;CACA,IAAI,OAAO,GACT,OAAO,UAAW,UAAW,IAAI,OAAS;CAE5C,OAAO;AACT;AAGA,SAAgB,OAAe;CAC7B,OAAO,WAAW,KAAK,IAAI,CAAC,IAAI,aAAa,YAAY,EAAE,CAAC;AAC9D;AAEA,SAAgB,QAAQ,OAAuB;CAC7C,OAAO,MACJ,YAAY,CAAC,CACb,WAAW,gBAAgB,GAAG,CAAC,CAC/B,WAAW,aAAa,EAAE,CAAC,CAC3B,MAAM,GAAG,EAAE;AAChB"}
1
+ {"version":3,"file":"ulid.mjs","names":[],"sources":["../../src/utils/ulid.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\n\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nfunction encodeTime(time: number): string {\n let out = \"\";\n let remaining = time;\n for (let i = 9; i >= 0; i -= 1) {\n const mod = remaining % 32;\n out = CROCKFORD.charAt(mod) + out;\n remaining = (remaining - mod) / 32;\n }\n return out;\n}\n\n/* eslint-disable no-bitwise, no-non-null-assertion -- ULID encoding is inherently bitwise. */\nfunction encodeRandom(bytes: Buffer): string {\n let out = \"\";\n let buffer = 0;\n let bits = 0;\n for (const byte of bytes) {\n buffer = (buffer << 8) | byte;\n bits += 8;\n while (bits >= 5) {\n out += CROCKFORD.charAt((buffer >>> (bits - 5)) & 31);\n bits -= 5;\n }\n }\n if (bits > 0) {\n out += CROCKFORD.charAt((buffer << (5 - bits)) & 31);\n }\n return out;\n}\n/* eslint-enable no-bitwise, no-non-null-assertion */\n\nexport function ulid(): string {\n return encodeTime(Date.now()) + encodeRandom(randomBytes(10));\n}\n\nexport function slugify(value: string): string {\n return value\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/gu, \"-\")\n .replaceAll(/^-+|-+$/gu, \"\")\n .slice(0, 63);\n}\n"],"mappings":";;;;;AAEA,MAAM,YAAY;AAElB,SAAS,WAAW,MAAsB;CACxC,IAAI,MAAM;CACV,IAAI,YAAY;CAChB,KAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG;EAC9B,MAAM,MAAM,YAAY;EACxB,MAAM,UAAU,OAAO,GAAG,IAAI;EAC9B,aAAa,YAAY,OAAO;CAClC;CACA,OAAO;AACT;AAGA,SAAS,aAAa,OAAuB;CAC3C,IAAI,MAAM;CACV,IAAI,SAAS;CACb,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OAAO;EACxB,SAAU,UAAU,IAAK;EACzB,QAAQ;EACR,OAAO,QAAQ,GAAG;GAChB,OAAO,UAAU,OAAQ,WAAY,OAAO,IAAM,EAAE;GACpD,QAAQ;EACV;CACF;CACA,IAAI,OAAO,GACT,OAAO,UAAU,OAAQ,UAAW,IAAI,OAAS,EAAE;CAErD,OAAO;AACT;AAGA,SAAgB,OAAe;CAC7B,OAAO,WAAW,KAAK,IAAI,CAAC,IAAI,aAAa,YAAY,EAAE,CAAC;AAC9D;AAEA,SAAgB,QAAQ,OAAuB;CAC7C,OAAO,MACJ,YAAY,CAAC,CACb,WAAW,gBAAgB,GAAG,CAAC,CAC/B,WAAW,aAAa,EAAE,CAAC,CAC3B,MAAM,GAAG,EAAE;AAChB"}