@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
@@ -6,10 +6,34 @@ import { renderSettingsGeneral } from "./settings-general.mjs";
6
6
  import { renderSettingsLabels } from "./settings-labels.mjs";
7
7
  import { renderSettingsMembers } from "./settings-members.mjs";
8
8
  import { renderSettingsStatus } from "./settings-status.mjs";
9
+ import { renderSettingsTests } from "./settings-tests.mjs";
9
10
  import { renderSettingsTokens } from "./settings-tokens.mjs";
10
11
  import { renderSettingsWebhooks } from "./settings-webhooks.mjs";
11
12
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
12
13
  //#region src/pages/project-settings.tsx
14
+ function tabHref(project, tab) {
15
+ return tab === "general" ? `/projects/${project.slug}/settings` : `/projects/${project.slug}/settings/${tab}`;
16
+ }
17
+ function renderTabLink(project, activeTab, tab, label) {
18
+ const active = activeTab === tab;
19
+ return /* @__PURE__ */ jsx("a", {
20
+ class: `tabs__link ${active ? "tabs__link--active" : ""}`,
21
+ href: tabHref(project, tab),
22
+ "aria-current": active ? "page" : void 0,
23
+ children: label
24
+ });
25
+ }
26
+ function renderActiveTab(data, formState) {
27
+ const { project, activeTab } = data;
28
+ if (activeTab === "general") return renderSettingsGeneral(project, formState, data.isAdmin);
29
+ if (activeTab === "tests") return renderSettingsTests(project, data.isAdmin, formState);
30
+ if (activeTab === "labels") return renderSettingsLabels(project, data.labelTypes, data.isAdmin);
31
+ if (activeTab === "tokens") return renderSettingsTokens(project, data.tokens, data.isAdmin);
32
+ if (activeTab === "webhooks") return renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState);
33
+ if (activeTab === "members") return renderSettingsMembers(project, data.members, data.isAdmin);
34
+ if (activeTab === "status") return renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin);
35
+ return null;
36
+ }
13
37
  function renderProjectSettingsPage(data, formState) {
14
38
  const { project, activeTab } = data;
15
39
  return /* @__PURE__ */ jsxs(DocumentLayout, {
@@ -58,50 +82,16 @@ function renderProjectSettingsPage(data, formState) {
58
82
  class: "tabs",
59
83
  "aria-label": "Settings sections",
60
84
  children: [
61
- /* @__PURE__ */ jsx("a", {
62
- class: `tabs__link ${activeTab === "general" ? "tabs__link--active" : ""}`,
63
- href: `/projects/${project.slug}/settings`,
64
- "aria-current": activeTab === "general" ? "page" : void 0,
65
- children: "General"
66
- }),
67
- /* @__PURE__ */ jsx("a", {
68
- class: `tabs__link ${activeTab === "labels" ? "tabs__link--active" : ""}`,
69
- href: `/projects/${project.slug}/settings/labels`,
70
- "aria-current": activeTab === "labels" ? "page" : void 0,
71
- children: "Labels"
72
- }),
73
- /* @__PURE__ */ jsx("a", {
74
- class: `tabs__link ${activeTab === "tokens" ? "tabs__link--active" : ""}`,
75
- href: `/projects/${project.slug}/settings/tokens`,
76
- "aria-current": activeTab === "tokens" ? "page" : void 0,
77
- children: "Tokens"
78
- }),
79
- /* @__PURE__ */ jsx("a", {
80
- class: `tabs__link ${activeTab === "webhooks" ? "tabs__link--active" : ""}`,
81
- href: `/projects/${project.slug}/settings/webhooks`,
82
- "aria-current": activeTab === "webhooks" ? "page" : void 0,
83
- children: "Webhooks"
84
- }),
85
- /* @__PURE__ */ jsx("a", {
86
- class: `tabs__link ${activeTab === "members" ? "tabs__link--active" : ""}`,
87
- href: `/projects/${project.slug}/settings/members`,
88
- "aria-current": activeTab === "members" ? "page" : void 0,
89
- children: "Members"
90
- }),
91
- /* @__PURE__ */ jsx("a", {
92
- class: `tabs__link ${activeTab === "status" ? "tabs__link--active" : ""}`,
93
- href: `/projects/${project.slug}/settings/status`,
94
- "aria-current": activeTab === "status" ? "page" : void 0,
95
- children: "Git status"
96
- })
85
+ renderTabLink(project, activeTab, "general", "General"),
86
+ renderTabLink(project, activeTab, "tests", "Tests"),
87
+ renderTabLink(project, activeTab, "labels", "Labels"),
88
+ renderTabLink(project, activeTab, "tokens", "Tokens"),
89
+ renderTabLink(project, activeTab, "webhooks", "Webhooks"),
90
+ renderTabLink(project, activeTab, "members", "Members"),
91
+ renderTabLink(project, activeTab, "status", "Git status")
97
92
  ]
98
93
  }),
99
- activeTab === "general" ? renderSettingsGeneral(project, formState, data.isAdmin) : null,
100
- activeTab === "labels" ? renderSettingsLabels(project, data.labelTypes, data.isAdmin) : null,
101
- activeTab === "tokens" ? renderSettingsTokens(project, data.tokens, data.isAdmin) : null,
102
- activeTab === "webhooks" ? renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState) : null,
103
- activeTab === "members" ? renderSettingsMembers(project, data.members, data.isAdmin) : null,
104
- activeTab === "status" ? renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin) : null
94
+ renderActiveTab(data, formState)
105
95
  ]
106
96
  });
107
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"project-settings.mjs","names":[],"sources":["../../src/pages/project-settings.tsx"],"sourcesContent":["import type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { LabelType, Project, Token } from \"../schema.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { renderSettingsGeneral } from \"./settings-general.tsx\";\nimport { renderSettingsLabels } from \"./settings-labels.tsx\";\nimport { renderSettingsMembers, type SettingsMember } from \"./settings-members.tsx\";\nimport { renderSettingsStatus, type SettingsStatusConfig } from \"./settings-status.tsx\";\nimport { renderSettingsTokens } from \"./settings-tokens.tsx\";\nimport { renderSettingsWebhooks, type SettingsWebhook } from \"./settings-webhooks.tsx\";\n\nexport type SettingsTab = \"general\" | \"labels\" | \"tokens\" | \"webhooks\" | \"members\" | \"status\";\n\nexport interface ProjectSettingsData {\n project: Project;\n activeTab: SettingsTab;\n labelTypes: LabelType[];\n tokens: Array<Omit<Token, \"hash\">>;\n members: SettingsMember[];\n webhooks: SettingsWebhook[];\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\nexport interface SettingsFormState {\n errors?: Record<string, string>;\n globalError?: string;\n secret?: string;\n}\n\nexport function renderProjectSettingsPage(data: ProjectSettingsData, formState?: SettingsFormState): RenderedContent {\n const { project, activeTab } = data;\n return (\n <DocumentLayout title={`${project.name} · Settings`} nav={{ active: \"settings\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </li>\n <li>\n <span aria-current=\"page\">Settings</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Project settings</h1>\n <p class=\"page-header__desc\">Manage general settings, labels, tokens, webhooks and members for {project.name}.</p>\n </div>\n </div>\n </div>\n\n <nav class=\"tabs\" aria-label=\"Settings sections\">\n <a class={`tabs__link ${activeTab === \"general\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings`} aria-current={activeTab === \"general\" ? \"page\" : undefined}>\n General\n </a>\n <a class={`tabs__link ${activeTab === \"labels\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/labels`} aria-current={activeTab === \"labels\" ? \"page\" : undefined}>\n Labels\n </a>\n <a class={`tabs__link ${activeTab === \"tokens\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/tokens`} aria-current={activeTab === \"tokens\" ? \"page\" : undefined}>\n Tokens\n </a>\n <a class={`tabs__link ${activeTab === \"webhooks\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/webhooks`} aria-current={activeTab === \"webhooks\" ? \"page\" : undefined}>\n Webhooks\n </a>\n <a class={`tabs__link ${activeTab === \"members\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/members`} aria-current={activeTab === \"members\" ? \"page\" : undefined}>\n Members\n </a>\n <a class={`tabs__link ${activeTab === \"status\" ? \"tabs__link--active\" : \"\"}`} href={`/projects/${project.slug}/settings/status`} aria-current={activeTab === \"status\" ? \"page\" : undefined}>\n Git status\n </a>\n </nav>\n\n {activeTab === \"general\" ? renderSettingsGeneral(project, formState, data.isAdmin) : null}\n {activeTab === \"labels\" ? renderSettingsLabels(project, data.labelTypes, data.isAdmin) : null}\n {activeTab === \"tokens\" ? renderSettingsTokens(project, data.tokens, data.isAdmin) : null}\n {activeTab === \"webhooks\" ? renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState) : null}\n {activeTab === \"members\" ? renderSettingsMembers(project, data.members, data.isAdmin) : null}\n {activeTab === \"status\" ? renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin) : null}\n </DocumentLayout>\n );\n}"],"mappings":";;;;;;;;;;;;AA8BA,SAAgB,0BAA0B,MAA2B,WAAgD;CACnH,MAAM,EAAE,SAAS,cAAc;CAC/B,OACE,qBAAC,gBAAD;EAAgB,OAAO,GAAG,QAAQ,KAAK;EAAc,KAAK;GAAE,QAAQ;GAAY,aAAa,QAAQ;GAAM,aAAa,QAAQ;EAAK;EAArI,UAAA;GACE,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA,CACE,oBAAC,OAAD;KAAK,OAAM;KAAc,cAAW;KAClC,UAAA,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAK;OAAY,UAAA;MAAW,CAAA,EAC7B,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAM,aAAa,QAAQ,KAAK;OAAW,UAAA,QAAQ;MAAQ,CAAA,EAC5D,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,QAAD;OAAM,gBAAa;OAAO,UAAA;MAAc,CAAA,EACtC,CAAA;KACF,EAAA,CAAA;IACD,CAAA,GACL,oBAAC,OAAD;KAAK,OAAM;KACT,UAAA,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,OAAM;MAAqB,UAAA;KAAoB,CAAA,GACnD,qBAAC,KAAD;MAAG,OAAM;MAAT,UAAA;OAA6B;OAAmE,QAAQ;OAAK;MAAI;KAC9G,CAAA,CAAA,EAAA,CAAA;IACF,CAAA,CACF;;GAEL,qBAAC,OAAD;IAAK,OAAM;IAAO,cAAW;IAA7B,UAAA;KACE,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,YAAY,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAY,gBAAc,cAAc,YAAY,SAAS,KAAA;MAAW,UAAA;KAEpL,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,WAAW,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAmB,gBAAc,cAAc,WAAW,SAAS,KAAA;MAAW,UAAA;KAEzL,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,WAAW,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAmB,gBAAc,cAAc,WAAW,SAAS,KAAA;MAAW,UAAA;KAEzL,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,aAAa,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAqB,gBAAc,cAAc,aAAa,SAAS,KAAA;MAAW,UAAA;KAE/L,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,YAAY,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAoB,gBAAc,cAAc,YAAY,SAAS,KAAA;MAAW,UAAA;KAE5L,CAAA;KACH,oBAAC,KAAD;MAAG,OAAO,cAAc,cAAc,WAAW,uBAAuB;MAAM,MAAM,aAAa,QAAQ,KAAK;MAAmB,gBAAc,cAAc,WAAW,SAAS,KAAA;MAAW,UAAA;KAEzL,CAAA;IACA;;GAEJ,cAAc,YAAY,sBAAsB,SAAS,WAAW,KAAK,OAAO,IAAI;GACpF,cAAc,WAAW,qBAAqB,SAAS,KAAK,YAAY,KAAK,OAAO,IAAI;GACxF,cAAc,WAAW,qBAAqB,SAAS,KAAK,QAAQ,KAAK,OAAO,IAAI;GACpF,cAAc,aAAa,uBAAuB,SAAS,KAAK,UAAU,KAAK,SAAS,SAAS,IAAI;GACrG,cAAc,YAAY,sBAAsB,SAAS,KAAK,SAAS,KAAK,OAAO,IAAI;GACvF,cAAc,WAAW,qBAAqB,SAAS,KAAK,eAAe,KAAK,UAAU,KAAK,OAAO,IAAI;EAC7F;;AAEpB"}
1
+ {"version":3,"file":"project-settings.mjs","names":[],"sources":["../../src/pages/project-settings.tsx"],"sourcesContent":["import type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { LabelType, Project, Token } from \"../schema.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { renderSettingsGeneral } from \"./settings-general.tsx\";\nimport { renderSettingsLabels } from \"./settings-labels.tsx\";\nimport { renderSettingsMembers, type SettingsMember } from \"./settings-members.tsx\";\nimport { renderSettingsStatus, type SettingsStatusConfig } from \"./settings-status.tsx\";\nimport { renderSettingsTests } from \"./settings-tests.tsx\";\nimport { renderSettingsTokens } from \"./settings-tokens.tsx\";\nimport { renderSettingsWebhooks, type SettingsWebhook } from \"./settings-webhooks.tsx\";\n\nexport type SettingsTab = \"general\" | \"tests\" | \"labels\" | \"tokens\" | \"webhooks\" | \"members\" | \"status\";\n\nexport interface ProjectSettingsData {\n project: Project;\n activeTab: SettingsTab;\n labelTypes: LabelType[];\n tokens: Omit<Token, \"hash\">[];\n members: SettingsMember[];\n webhooks: SettingsWebhook[];\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\nexport interface SettingsFormState {\n errors?: Record<string, string>;\n globalError?: string;\n secret?: string;\n}\n\nfunction tabHref(project: Project, tab: SettingsTab): string {\n return tab === \"general\" ? `/projects/${project.slug}/settings` : `/projects/${project.slug}/settings/${tab}`;\n}\n\nfunction renderTabLink(project: Project, activeTab: SettingsTab, tab: SettingsTab, label: string): unknown {\n const active = activeTab === tab;\n return (\n <a class={`tabs__link ${active ? \"tabs__link--active\" : \"\"}`} href={tabHref(project, tab)} aria-current={active ? \"page\" : undefined}>\n {label}\n </a>\n );\n}\n\nfunction renderActiveTab(data: ProjectSettingsData, formState?: SettingsFormState): unknown {\n const { project, activeTab } = data;\n if (activeTab === \"general\") return renderSettingsGeneral(project, formState, data.isAdmin);\n if (activeTab === \"tests\") return renderSettingsTests(project, data.isAdmin, formState);\n if (activeTab === \"labels\") return renderSettingsLabels(project, data.labelTypes, data.isAdmin);\n if (activeTab === \"tokens\") return renderSettingsTokens(project, data.tokens, data.isAdmin);\n if (activeTab === \"webhooks\") return renderSettingsWebhooks(project, data.webhooks, data.isAdmin, formState);\n if (activeTab === \"members\") return renderSettingsMembers(project, data.members, data.isAdmin);\n if (activeTab === \"status\") return renderSettingsStatus(project, data.statusConfigs, data.gitHosts, data.isAdmin);\n return null;\n}\n\nexport function renderProjectSettingsPage(data: ProjectSettingsData, formState?: SettingsFormState): RenderedContent {\n const { project, activeTab } = data;\n return (\n <DocumentLayout title={`${project.name} · Settings`} nav={{ active: \"settings\", projectSlug: project.slug, projectName: project.name }}>\n <div class=\"page-header\">\n <nav class=\"breadcrumbs\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"/projects\">Projects</a>\n </li>\n <li>\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </li>\n <li>\n <span aria-current=\"page\">Settings</span>\n </li>\n </ol>\n </nav>\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Project settings</h1>\n <p class=\"page-header__desc\">Manage general settings, labels, tokens, webhooks and members for {project.name}.</p>\n </div>\n </div>\n </div>\n\n <nav class=\"tabs\" aria-label=\"Settings sections\">\n {renderTabLink(project, activeTab, \"general\", \"General\")}\n {renderTabLink(project, activeTab, \"tests\", \"Tests\")}\n {renderTabLink(project, activeTab, \"labels\", \"Labels\")}\n {renderTabLink(project, activeTab, \"tokens\", \"Tokens\")}\n {renderTabLink(project, activeTab, \"webhooks\", \"Webhooks\")}\n {renderTabLink(project, activeTab, \"members\", \"Members\")}\n {renderTabLink(project, activeTab, \"status\", \"Git status\")}\n </nav>\n\n {renderActiveTab(data, formState)}\n </DocumentLayout>\n );\n}"],"mappings":";;;;;;;;;;;;;AA+BA,SAAS,QAAQ,SAAkB,KAA0B;CAC3D,OAAO,QAAQ,YAAY,aAAa,QAAQ,KAAK,aAAa,aAAa,QAAQ,KAAK,YAAY;AAC1G;AAEA,SAAS,cAAc,SAAkB,WAAwB,KAAkB,OAAwB;CACzG,MAAM,SAAS,cAAc;CAC7B,OACE,oBAAC,KAAD;EAAG,OAAO,cAAc,SAAS,uBAAuB;EAAM,MAAM,QAAQ,SAAS,GAAG;EAAG,gBAAc,SAAS,SAAS,KAAA;EACxH,UAAA;CACA,CAAA;AAEP;AAEA,SAAS,gBAAgB,MAA2B,WAAwC;CAC1F,MAAM,EAAE,SAAS,cAAc;CAC/B,IAAI,cAAc,WAAW,OAAO,sBAAsB,SAAS,WAAW,KAAK,OAAO;CAC1F,IAAI,cAAc,SAAS,OAAO,oBAAoB,SAAS,KAAK,SAAS,SAAS;CACtF,IAAI,cAAc,UAAU,OAAO,qBAAqB,SAAS,KAAK,YAAY,KAAK,OAAO;CAC9F,IAAI,cAAc,UAAU,OAAO,qBAAqB,SAAS,KAAK,QAAQ,KAAK,OAAO;CAC1F,IAAI,cAAc,YAAY,OAAO,uBAAuB,SAAS,KAAK,UAAU,KAAK,SAAS,SAAS;CAC3G,IAAI,cAAc,WAAW,OAAO,sBAAsB,SAAS,KAAK,SAAS,KAAK,OAAO;CAC7F,IAAI,cAAc,UAAU,OAAO,qBAAqB,SAAS,KAAK,eAAe,KAAK,UAAU,KAAK,OAAO;CAChH,OAAO;AACT;AAEA,SAAgB,0BAA0B,MAA2B,WAAgD;CACnH,MAAM,EAAE,SAAS,cAAc;CAC/B,OACE,qBAAC,gBAAD;EAAgB,OAAO,GAAG,QAAQ,KAAK;EAAc,KAAK;GAAE,QAAQ;GAAY,aAAa,QAAQ;GAAM,aAAa,QAAQ;EAAK;EAArI,UAAA;GACE,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA,CACE,oBAAC,OAAD;KAAK,OAAM;KAAc,cAAW;KAClC,UAAA,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAK;OAAY,UAAA;MAAW,CAAA,EAC7B,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,KAAD;OAAG,MAAM,aAAa,QAAQ,KAAK;OAAW,UAAA,QAAQ;MAAQ,CAAA,EAC5D,CAAA;MACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,QAAD;OAAM,gBAAa;OAAO,UAAA;MAAc,CAAA,EACtC,CAAA;KACF,EAAA,CAAA;IACD,CAAA,GACL,oBAAC,OAAD;KAAK,OAAM;KACT,UAAA,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,OAAM;MAAqB,UAAA;KAAoB,CAAA,GACnD,qBAAC,KAAD;MAAG,OAAM;MAAT,UAAA;OAA6B;OAAmE,QAAQ;OAAK;MAAI;KAC9G,CAAA,CAAA,EAAA,CAAA;IACF,CAAA,CACF;;GAEL,qBAAC,OAAD;IAAK,OAAM;IAAO,cAAW;IAA7B,UAAA;KACG,cAAc,SAAS,WAAW,WAAW,SAAS;KACtD,cAAc,SAAS,WAAW,SAAS,OAAO;KAClD,cAAc,SAAS,WAAW,UAAU,QAAQ;KACpD,cAAc,SAAS,WAAW,UAAU,QAAQ;KACpD,cAAc,SAAS,WAAW,YAAY,UAAU;KACxD,cAAc,SAAS,WAAW,WAAW,SAAS;KACtD,cAAc,SAAS,WAAW,UAAU,YAAY;IACtD;;GAEJ,gBAAgB,MAAM,SAAS;EAClB;;AAEpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"projects.mjs","names":[],"sources":["../../src/pages/projects.tsx"],"sourcesContent":["import { ProjectModel } from \"../models/project.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport async function renderProjectsPage(): Promise<RenderedContent> {\n const { db, user } = getStore();\n const projects = await new ProjectModel(db).list();\n const canCreate = !user || user.role === \"admin\" || user.role === \"member\";\n\n const recentCounts = await Promise.all(\n projects.map(async (project) => {\n const builds = await new BuildModel(db).list(project.id);\n return { slug: project.slug, count: builds.length, latest: builds[0] ?? null };\n }),\n );\n const countsBySlug = new Map(recentCounts.map((entry) => [entry.slug, entry]));\n\n return (\n <DocumentLayout title=\"Projects\" nav={{ active: \"projects\" }}>\n <div class=\"page-header\">\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Projects</h1>\n <p class=\"page-header__desc\">Each project is one Storybook. Create a project, then upload builds from CI.</p>\n </div>\n <div class=\"page-header__actions\">\n {canCreate ? (\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n New project\n </a>\n ) : null}\n </div>\n </div>\n </div>\n\n {projects.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No projects yet</h2>\n <p class=\"empty__desc\">Create your first project to start visual testing. Projects are free and unlimited.</p>\n {canCreate ? (\n <div class=\"empty__action\">\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n Create project\n </a>\n </div>\n ) : null}\n </div>\n ) : (\n <div class=\"grid\" style=\"gap:.75rem;\">\n {projects.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (project) => {\n const info = countsBySlug.get(project.slug);\n return (\n <div key={project.id} class=\"card card--padded\">\n <div style=\"display:flex; justify-content:space-between; gap:1rem; flex-wrap:wrap; align-items:flex-start;\">\n <div style=\"min-width:0;\">\n <h2 style=\"margin:0; font-size:1.15rem;\">\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </h2>\n <p class=\"field__hint\" style=\"margin:.2rem 0 0;\">\n <code>{project.slug}</code> {project.gitRepository ? `· ${project.gitRepository}` : \"\"} · default <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </p>\n {info?.latest ? (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n Latest: {info.latest.gitBranch} · {info.latest.gitSha.slice(0, 7)} · <Badge tone={info.latest.status === \"approved\" ? \"success\" : info.latest.status === \"reviewing\" ? \"warning\" : \"neutral\"}>{info.latest.status}</Badge>\n </p>\n ) : (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n No builds yet.\n </p>\n )}\n </div>\n <div style=\"display:flex; gap:.5rem; flex-wrap:wrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds`}>\n Builds {info?.count ? `(${info.count})` : \"\"}\n </a>\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/settings`}>\n Settings\n </a>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n <div class=\"card card--padded\" style=\"margin-top:1rem;\">\n <h3 style=\"margin:0 0 .4rem;\">Next steps</h3>\n <ol style=\"margin:.4rem 0 0; padding-left:1.2rem; color:var(--text-secondary);\">\n <li>\n Create a project (or run <code>npx @storyshelf/cli create --url http://localhost:3000 --name \"My Storybook\" --token $STORYSHELF_ADMIN_TOKEN</code>, or <code>init --url --slug</code> to write <code>.storybook/storyshelf.json</code>)\n </li>\n <li>\n Generate a token in <strong>Settings → Tokens</strong> and set <code>STORYSHELF_TOKEN</code> in CI.\n </li>\n <li>\n Upload: <code>npx @storyshelf/cli upload</code> (or <code>npx @storyshelf/cli</code> defaults to upload when config exists)\n </li>\n </ol>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAMA,eAAsB,qBAA+C;CACnE,MAAM,EAAE,IAAI,SAAS,SAAS;CAC9B,MAAM,WAAW,MAAM,IAAI,aAAa,EAAE,CAAC,CAAC,KAAK;CACjD,MAAM,YAAY,CAAC,QAAQ,KAAK,SAAS,WAAW,KAAK,SAAS;CAElE,MAAM,eAAe,MAAM,QAAQ,IACjC,SAAS,IAAI,OAAO,YAAY;EAC9B,MAAM,SAAS,MAAM,IAAI,WAAW,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE;EACvD,OAAO;GAAE,MAAM,QAAQ;GAAM,OAAO,OAAO;GAAQ,QAAQ,OAAO,MAAM;EAAK;CAC/E,CAAC,CACH;CACA,MAAM,eAAe,IAAI,IAAI,aAAa,KAAK,UAAU,CAAC,MAAM,MAAM,KAAK,CAAC,CAAC;CAE7E,OACE,qBAAC,gBAAD;EAAgB,OAAM;EAAW,KAAK,EAAE,QAAQ,WAAW;EAA3D,UAAA;GACE,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,qBAAC,OAAD;KAAK,OAAM;KAAX,UAAA,CACE,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,OAAM;MAAqB,UAAA;KAAY,CAAA,GAC3C,oBAAC,KAAD;MAAG,OAAM;MAAoB,UAAA;KAA+E,CAAA,CACzG,EAAA,CAAA,GACL,oBAAC,OAAD;MAAK,OAAM;MACR,UAAA,YACC,oBAAC,KAAD;OAAG,OAAM;OAAmB,MAAK;OAAgB,UAAA;MAE9C,CAAA,IACD;KACD,CAAA,CACF;;GACF,CAAA;GAEJ,SAAS,WAAW,IACnB,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA;KACE,oBAAC,MAAD;MAAI,OAAM;MAAe,UAAA;KAAmB,CAAA;KAC5C,oBAAC,KAAD;MAAG,OAAM;MAAc,UAAA;KAAsF,CAAA;KAC5G,YACC,oBAAC,OAAD;MAAK,OAAM;MACT,UAAA,oBAAC,KAAD;OAAG,OAAM;OAAmB,MAAK;OAAgB,UAAA;MAE9C,CAAA;KACA,CAAA,IACH;IACD;GAEL,CAAA,IAAA,oBAAC,OAAD;IAAK,OAAM;IAAO,OAAM;IACrB,UAAA,SAAS,KAEP,YAAY;KACb,MAAM,OAAO,aAAa,IAAI,QAAQ,IAAI;KAC1C,OACE,oBAAC,OAAD;MAAsB,OAAM;MAC1B,UAAA,qBAAC,OAAD;OAAK,OAAM;OAAX,UAAA,CACE,qBAAC,OAAD;QAAK,OAAM;QAAX,UAAA;SACE,oBAAC,MAAD;UAAI,OAAM;UACR,UAAA,oBAAC,KAAD;WAAG,MAAM,aAAa,QAAQ,KAAK;WAAW,UAAA,QAAQ;UAAQ,CAAA;SAC5D,CAAA;SACJ,qBAAC,KAAD;UAAG,OAAM;UAAc,OAAM;UAA7B,UAAA;WACE,oBAAC,QAAD,EAAA,UAAO,QAAQ,KAAW,CAAA;WAAC;WAAE,QAAQ,gBAAgB,KAAK,QAAQ,kBAAkB;WAAG;WAAW,oBAAC,OAAD;YAAO,MAAK;YAAW,UAAA,QAAQ;WAAwB,CAAA;UACxJ;;SACF,MAAM,SACL,qBAAC,KAAD;UAAG,OAAM;UAAc,OAAM;UAA7B,UAAA;WAAiD;WACtC,KAAK,OAAO;WAAU;WAAI,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC;WAAE;WAAG,oBAAC,OAAD;YAAO,MAAM,KAAK,OAAO,WAAW,aAAa,YAAY,KAAK,OAAO,WAAW,cAAc,YAAY;YAAY,UAAA,KAAK,OAAO;WAAc,CAAA;UACxN;SAEH,CAAA,IAAA,oBAAC,KAAD;UAAG,OAAM;UAAc,OAAM;UAAoB,UAAA;SAE9C,CAAA;QAEF;OACL,CAAA,GAAA,qBAAC,OAAD;QAAK,OAAM;QAAX,UAAA,CACE,qBAAC,KAAD;SAAG,OAAM;SAAqB,MAAM,aAAa,QAAQ,KAAK;SAA9D,UAAA,CAAwE,WAC9D,MAAM,QAAQ,IAAI,KAAK,MAAM,KAAK,EACzC;QACH,CAAA,GAAA,oBAAC,KAAD;SAAG,OAAM;SAAiB,MAAM,aAAa,QAAQ,KAAK;SAAY,UAAA;QAEnE,CAAA,CACA;OACF,CAAA,CAAA;;KACF,GA5BK,QAAQ,EA4Bb;IAET,CAAC;GACE,CAAA;GAGP,qBAAC,OAAD;IAAK,OAAM;IAAoB,OAAM;IAArC,UAAA,CACE,oBAAC,MAAD;KAAI,OAAM;KAAoB,UAAA;IAAc,CAAA,GAC5C,qBAAC,MAAD;KAAI,OAAM;KAAV,UAAA;MACE,qBAAC,MAAD,EAAA,UAAA;OAAI;OACuB,oBAAC,QAAD,EAAA,UAAM,iHAAkH,CAAA;OAAC;OAAK,oBAAC,QAAD,EAAA,UAAM,oBAAuB,CAAA;OAAC;OAAU,oBAAC,QAAD,EAAA,UAAM,6BAAgC,CAAA;OAAC;MACpO,EAAA,CAAA;MACJ,qBAAC,MAAD,EAAA,UAAA;OAAI;OACkB,oBAAC,UAAD,EAAA,UAAQ,oBAAyB,CAAA;OAAC;OAAS,oBAAC,QAAD,EAAA,UAAM,mBAAsB,CAAA;OAAC;MAC1F,EAAA,CAAA;MACJ,qBAAC,MAAD,EAAA,UAAA;OAAI;OACM,oBAAC,QAAD,EAAA,UAAM,6BAAgC,CAAA;OAAC;OAAK,oBAAC,QAAD,EAAA,UAAM,sBAAyB,CAAA;OAAC;MAClF,EAAA,CAAA;KACF;IACD,CAAA,CAAA;;EACS;;AAEpB"}
1
+ {"version":3,"file":"projects.mjs","names":[],"sources":["../../src/pages/projects.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { getStore } from \"../store.ts\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport async function renderProjectsPage(): Promise<RenderedContent> {\n const { db, user } = getStore();\n const projects = await new ProjectModel(db).list();\n const canCreate = !user || user.role === \"admin\" || user.role === \"member\";\n\n const recentCounts = await Promise.all(\n projects.map(async (project) => {\n const builds = await new BuildModel(db).list(project.id);\n return { slug: project.slug, count: builds.length, latest: builds[0] ?? null };\n }),\n );\n const countsBySlug = new Map(recentCounts.map((entry) => [entry.slug, entry]));\n\n return (\n <DocumentLayout title=\"Projects\" nav={{ active: \"projects\" }}>\n <div class=\"page-header\">\n <div class=\"page-header__row\">\n <div>\n <h1 class=\"page-header__title\">Projects</h1>\n <p class=\"page-header__desc\">Each project is one Storybook. Create a project, then upload builds from CI.</p>\n </div>\n <div class=\"page-header__actions\">\n {canCreate ? (\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n New project\n </a>\n ) : null}\n </div>\n </div>\n </div>\n\n {projects.length === 0 ? (\n <div class=\"empty\">\n <h2 class=\"empty__title\">No projects yet</h2>\n <p class=\"empty__desc\">Create your first project to start visual testing. Projects are free and unlimited.</p>\n {canCreate ? (\n <div class=\"empty__action\">\n <a class=\"btn btn--primary\" href=\"/projects/new\">\n Create project\n </a>\n </div>\n ) : null}\n </div>\n ) : (\n <div class=\"grid\" style=\"gap:.75rem;\">\n {projects.map(\n (project): HtmlEscapedString | Promise<HtmlEscapedString> => {\n const info = countsBySlug.get(project.slug);\n return (\n <div key={project.id} class=\"card card--padded\">\n <div style=\"display:flex; justify-content:space-between; gap:1rem; flex-wrap:wrap; align-items:flex-start;\">\n <div style=\"min-width:0;\">\n <h2 style=\"margin:0; font-size:1.15rem;\">\n <a href={`/projects/${project.slug}/builds`}>{project.name}</a>\n </h2>\n <p class=\"field__hint\" style=\"margin:.2rem 0 0;\">\n <code>{project.slug}</code> {project.gitRepository ? `· ${project.gitRepository}` : \"\"} · default <Badge tone=\"neutral\">{project.gitDefaultBranch}</Badge>\n </p>\n {info?.latest ? (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n Latest: {info.latest.gitBranch} · {info.latest.gitSha.slice(0, 7)} · <Badge tone={info.latest.status === \"approved\" ? \"success\" : (info.latest.status === \"reviewing\" ? \"warning\" : \"neutral\")}>{info.latest.status}</Badge>\n </p>\n ) : (\n <p class=\"field__hint\" style=\"margin:.3rem 0 0;\">\n No builds yet.\n </p>\n )}\n </div>\n <div style=\"display:flex; gap:.5rem; flex-wrap:wrap;\">\n <a class=\"btn btn--secondary\" href={`/projects/${project.slug}/builds`}>\n Builds {info?.count ? `(${info.count})` : \"\"}\n </a>\n <a class=\"btn btn--ghost\" href={`/projects/${project.slug}/settings`}>\n Settings\n </a>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n <div class=\"card card--padded\" style=\"margin-top:1rem;\">\n <h3 style=\"margin:0 0 .4rem;\">Next steps</h3>\n <ol style=\"margin:.4rem 0 0; padding-left:1.2rem; color:var(--text-secondary);\">\n <li>\n Create a project (or run <code>npx @storyshelf/cli create --url http://localhost:3000 --name \"My Storybook\" --token $STORYSHELF_ADMIN_TOKEN</code>, or <code>init --url --slug</code> to write <code>.storybook/storyshelf.json</code>)\n </li>\n <li>\n Generate a token in <strong>Settings → Tokens</strong> and set <code>STORYSHELF_TOKEN</code> in CI.\n </li>\n <li>\n Upload: <code>npx @storyshelf/cli upload</code> (or <code>npx @storyshelf/cli</code> defaults to upload when config exists)\n </li>\n </ol>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings-labels.mjs","names":[],"sources":["../../src/pages/settings-labels.tsx"],"sourcesContent":["import type { LabelType, Project } from \"../schema.ts\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport function renderSettingsLabels(project: Project, labelTypes: LabelType[], isAdmin: boolean): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">Label types</h2>\n <p class=\"field__hint\">Labels attach typed values to builds (e.g. pr=123, jira=ABC-123). Values link out via the template.</p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>Key</th>\n <th>Name</th>\n <th>Template</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {labelTypes.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (labelType) => (\n <tr key={labelType.id}>\n <td>\n <Badge tone=\"neutral\">{labelType.key}</Badge>\n </td>\n <td>{labelType.name}</td>\n <td style=\"max-width:32ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\">{labelType.linkTemplate ?? \"—\"}</td>\n <td>\n {isAdmin && labelType.key !== \"persistent\" && labelType.key !== \"branch\" ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/labels/${labelType.key}/delete`} hx-post={`/projects/${project.slug}/settings/labels/${labelType.key}/delete`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\" aria-label={`Delete ${labelType.key}`}>\n Delete\n </button>\n </form>\n ) : (\n <span class=\"field__hint\">built-in</span>\n )}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {labelTypes.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No label types configured.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Create label type</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/labels`} hx-post={`/projects/${project.slug}/settings/labels`} hx-target=\"body\">\n <div class=\"grid grid--2\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"key\">\n Key\n </label>\n <input class=\"field__input\" id=\"key\" name=\"key\" required placeholder=\"jira\" pattern=\"^[a-z0-9_-]+$\" />\n <p class=\"field__hint\">Lowercase, no spaces.</p>\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"labelName\">\n Name\n </label>\n <input class=\"field__input\" id=\"labelName\" name=\"labelName\" required placeholder=\"Jira issue\" />\n </div>\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"linkTemplate\">\n Link template\n </label>\n <input class=\"field__input\" id=\"linkTemplate\" name=\"linkTemplate\" placeholder=\"https://jira.example.com/browse/{value}\" />\n <p class=\"field__hint\">\n Use {\"{value}\"} placeholder. Optional.\n </p>\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Add label type\n </button>\n </form>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":";;;;;;AAGA,SAAgB,qBAAqB,SAAkB,YAAyB,SAA2B;CACzG,OACE,qBAAC,OAAD;EAAK,OAAM;EAAO,OAAM;EAAxB,UAAA,CACE,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAI,OAAM;KAAoB,UAAA;IAAe,CAAA;IAC7C,oBAAC,KAAD;KAAG,OAAM;KAAc,UAAA;IAAsG,CAAA;IAC7H,oBAAC,OAAD;KAAK,OAAM;KAAa,OAAM;KAC5B,UAAA,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UAAI,MAAO,CAAA;MACX,oBAAC,MAAD,EAAA,UAAI,OAAQ,CAAA;MACZ,oBAAC,MAAD,EAAA,UAAI,WAAY,CAAA;MAChB,oBAAC,MAAD,CAAQ,CAAA;KACN,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,WAAW,KAET,cACC,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UACE,oBAAC,OAAD;OAAO,MAAK;OAAW,UAAA,UAAU;MAAW,CAAA,EAC1C,CAAA;MACJ,oBAAC,MAAD,EAAA,UAAK,UAAU,KAAS,CAAA;MACxB,oBAAC,MAAD;OAAI,OAAM;OAAgF,UAAA,UAAU,gBAAgB;MAAQ,CAAA;MAC5H,oBAAC,MAAD,EAAA,UACG,WAAW,UAAU,QAAQ,gBAAgB,UAAU,QAAQ,WAC9D,oBAAC,QAAD;OAAM,QAAO;OAAO,QAAQ,aAAa,QAAQ,KAAK,mBAAmB,UAAU,IAAI;OAAU,WAAS,aAAa,QAAQ,KAAK,mBAAmB,UAAU,IAAI;OAAU,aAAU;OACvL,UAAA,oBAAC,UAAD;QAAQ,OAAM;QAAiB,MAAK;QAAS,cAAY,UAAU,UAAU;QAAO,UAAA;OAE5E,CAAA;MACJ,CAAA,IAEN,oBAAC,QAAD;OAAM,OAAM;OAAc,UAAA;MAAc,CAAA,EAExC,CAAA;KACF,EAAA,GAjBK,UAAU,EAiBf,CAER,EACK,CAAA,CACF,EAAA,CAAA;IACJ,CAAA;IACJ,WAAW,WAAW,IAAI,oBAAC,KAAD;KAAG,OAAM;KAAc,OAAM;KAAoB,UAAA;IAA6B,CAAA,IAAI;GAC1G;EAEJ,CAAA,GAAA,UACC,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA,CACE,oBAAC,MAAD;IAAI,OAAM;IAAoB,UAAA;GAAqB,CAAA,GACnD,qBAAC,QAAD;IAAM,QAAO;IAAO,QAAQ,aAAa,QAAQ,KAAK;IAAmB,WAAS,aAAa,QAAQ,KAAK;IAAmB,aAAU;IAAzI,UAAA;KACE,qBAAC,OAAD;MAAK,OAAM;MAAX,UAAA,CACE,qBAAC,OAAD;OAAK,OAAM;OAAX,UAAA;QACE,oBAAC,SAAD;SAAO,OAAM;SAAe,KAAI;SAAM,UAAA;QAE/B,CAAA;QACP,oBAAC,SAAD;SAAO,OAAM;SAAe,IAAG;SAAM,MAAK;SAAM,UAAA;SAAS,aAAY;SAAO,SAAQ;QAAiB,CAAA;QACrG,oBAAC,KAAD;SAAG,OAAM;SAAc,UAAA;QAAwB,CAAA;OAC5C;MACL,CAAA,GAAA,qBAAC,OAAD;OAAK,OAAM;OAAX,UAAA,CACE,oBAAC,SAAD;QAAO,OAAM;QAAe,KAAI;QAAY,UAAA;OAErC,CAAA,GACP,oBAAC,SAAD;QAAO,OAAM;QAAe,IAAG;QAAY,MAAK;QAAY,UAAA;QAAS,aAAY;OAAc,CAAA,CAC5F;MACF,CAAA,CAAA;;KACL,qBAAC,OAAD;MAAK,OAAM;MAAX,UAAA;OACE,oBAAC,SAAD;QAAO,OAAM;QAAe,KAAI;QAAe,UAAA;OAExC,CAAA;OACP,oBAAC,SAAD;QAAO,OAAM;QAAe,IAAG;QAAe,MAAK;QAAe,aAAY;OAA2C,CAAA;OACzH,qBAAC,KAAD;QAAG,OAAM;QAAT,UAAA;SAAuB;SAChB;SAAU;QACd;;MACA;;KACL,oBAAC,UAAD;MAAQ,OAAM;MAAmB,MAAK;MAAS,UAAA;KAEvC,CAAA;IACJ;GACH,CAAA,CAAA;EACH,CAAA,IAAA,IACD;;AAET"}
1
+ {"version":3,"file":"settings-labels.mjs","names":[],"sources":["../../src/pages/settings-labels.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { LabelType, Project } from \"../schema.ts\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport function renderSettingsLabels(project: Project, labelTypes: LabelType[], isAdmin: boolean): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">Label types</h2>\n <p class=\"field__hint\">Labels attach typed values to builds (e.g. pr=123, jira=ABC-123). Values link out via the template.</p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>Key</th>\n <th>Name</th>\n <th>Template</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {labelTypes.map(\n (labelType): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <tr key={labelType.id}>\n <td>\n <Badge tone=\"neutral\">{labelType.key}</Badge>\n </td>\n <td>{labelType.name}</td>\n <td style=\"max-width:32ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\">{labelType.linkTemplate ?? \"—\"}</td>\n <td>\n {isAdmin && labelType.key !== \"persistent\" && labelType.key !== \"branch\" ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/labels/${labelType.key}/delete`} hx-post={`/projects/${project.slug}/settings/labels/${labelType.key}/delete`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\" aria-label={`Delete ${labelType.key}`}>\n Delete\n </button>\n </form>\n ) : (\n <span class=\"field__hint\">built-in</span>\n )}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {labelTypes.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No label types configured.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Create label type</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/labels`} hx-post={`/projects/${project.slug}/settings/labels`} hx-target=\"body\">\n <div class=\"grid grid--2\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"key\">\n Key\n </label>\n <input class=\"field__input\" id=\"key\" name=\"key\" required placeholder=\"jira\" pattern=\"^[a-z0-9_-]+$\" />\n <p class=\"field__hint\">Lowercase, no spaces.</p>\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"labelName\">\n Name\n </label>\n <input class=\"field__input\" id=\"labelName\" name=\"labelName\" required placeholder=\"Jira issue\" />\n </div>\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"linkTemplate\">\n Link template\n </label>\n <input class=\"field__input\" id=\"linkTemplate\" name=\"linkTemplate\" placeholder=\"https://jira.example.com/browse/{value}\" />\n <p class=\"field__hint\">\n Use {\"{value}\"} placeholder. Optional.\n </p>\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Add label type\n </button>\n </form>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings-members.mjs","names":[],"sources":["../../src/pages/settings-members.tsx"],"sourcesContent":["import type { Project } from \"../schema.ts\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport interface SettingsMember {\n id: string;\n projectId: string;\n userId: string;\n role: string;\n createdAt: string;\n}\n\nexport function renderSettingsMembers(project: Project, members: SettingsMember[], isAdmin: boolean): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">Members</h2>\n <p class=\"field__hint\">Project members and their roles. Site admins have implicit admin access.</p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>User</th>\n <th>Role</th>\n <th>Since</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {members.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (member) => (\n <tr key={member.id}>\n <td>\n <code>{member.userId}</code>\n </td>\n <td>\n <Badge tone={member.role === \"admin\" ? \"danger\" : member.role === \"viewer\" ? \"neutral\" : \"info\"}>{member.role}</Badge>\n </td>\n <td>{new Date(member.createdAt).toLocaleDateString()}</td>\n <td>\n {isAdmin ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/members/${member.userId}/remove`} hx-post={`/projects/${project.slug}/settings/members/${member.userId}/remove`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\">\n Remove\n </button>\n </form>\n ) : null}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {members.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No members yet.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Add member</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/members`} hx-post={`/projects/${project.slug}/settings/members`} hx-target=\"body\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"userId\">\n User ID\n </label>\n <input class=\"field__input\" id=\"userId\" name=\"userId\" required placeholder=\"user_...\" />\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"role\">\n Role\n </label>\n <select class=\"field__input\" id=\"role\" name=\"role\">\n <option value=\"viewer\">viewer</option>\n <option value=\"developer\">developer</option>\n <option value=\"approver\">approver</option>\n <option value=\"admin\">admin</option>\n </select>\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Add member\n </button>\n </form>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":"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"}
1
+ {"version":3,"file":"settings-members.mjs","names":[],"sources":["../../src/pages/settings-members.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { Project } from \"../schema.ts\";\nimport { Badge } from \"../ui/components.tsx\";\n\nexport interface SettingsMember {\n id: string;\n projectId: string;\n userId: string;\n role: string;\n createdAt: string;\n}\n\nexport function renderSettingsMembers(project: Project, members: SettingsMember[], isAdmin: boolean): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">Members</h2>\n <p class=\"field__hint\">Project members and their roles. Site admins have implicit admin access.</p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>User</th>\n <th>Role</th>\n <th>Since</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {members.map(\n (member): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <tr key={member.id}>\n <td>\n <code>{member.userId}</code>\n </td>\n <td>\n <Badge tone={member.role === \"admin\" ? \"danger\" : (member.role === \"viewer\" ? \"neutral\" : \"info\")}>{member.role}</Badge>\n </td>\n <td>{new Date(member.createdAt).toLocaleDateString()}</td>\n <td>\n {isAdmin ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/members/${member.userId}/remove`} hx-post={`/projects/${project.slug}/settings/members/${member.userId}/remove`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\">\n Remove\n </button>\n </form>\n ) : null}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {members.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No members yet.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Add member</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/members`} hx-post={`/projects/${project.slug}/settings/members`} hx-target=\"body\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"userId\">\n User ID\n </label>\n <input class=\"field__input\" id=\"userId\" name=\"userId\" required placeholder=\"user_...\" />\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"role\">\n Role\n </label>\n <select class=\"field__input\" id=\"role\" name=\"role\">\n <option value=\"viewer\">viewer</option>\n <option value=\"developer\">developer</option>\n <option value=\"approver\">approver</option>\n <option value=\"admin\">admin</option>\n </select>\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Add member\n </button>\n </form>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
5
+ //#region src/pages/settings-tests.tsx
6
+ function renderSettingsTests(project, isAdmin, formState) {
7
+ return /* @__PURE__ */ jsxs("div", {
8
+ class: "grid",
9
+ style: "max-width: 720px;",
10
+ children: [
11
+ formState?.globalError ? /* @__PURE__ */ jsx("div", {
12
+ class: "alert alert--danger",
13
+ role: "alert",
14
+ children: formState.globalError
15
+ }) : null,
16
+ /* @__PURE__ */ jsxs("div", {
17
+ class: "card card--padded",
18
+ children: [
19
+ /* @__PURE__ */ jsx("h2", {
20
+ style: "margin:0 0 .5rem;",
21
+ children: "Interaction tests"
22
+ }),
23
+ /* @__PURE__ */ jsxs("p", {
24
+ class: "field__hint",
25
+ style: "margin-bottom:1rem;",
26
+ children: [
27
+ "When enabled, Storybook ",
28
+ /* @__PURE__ */ jsx("code", { children: "play" }),
29
+ " functions run before each screenshot. Failures block the build unless the story is marked",
30
+ /* @__PURE__ */ jsx("code", { children: "flaky-test" }),
31
+ " via ",
32
+ /* @__PURE__ */ jsx("code", { children: "tags: ['flaky-test']" }),
33
+ " or",
34
+ /* @__PURE__ */ jsx("code", { children: "parameters: { flakyTest: true }" }),
35
+ " (supports both ",
36
+ /* @__PURE__ */ jsx("code", { children: "chromatic" }),
37
+ " and ",
38
+ /* @__PURE__ */ jsx("code", { children: "storyshelf" }),
39
+ " keys, story-level, case-insensitive). Use",
40
+ " ",
41
+ /* @__PURE__ */ jsx("code", { children: "disableSnapshot" }),
42
+ " to skip a story entirely."
43
+ ]
44
+ }),
45
+ /* @__PURE__ */ jsxs("form", {
46
+ method: "post",
47
+ action: `/projects/${project.slug}/settings/tests`,
48
+ "hx-post": `/projects/${project.slug}/settings/tests`,
49
+ "hx-target": "body",
50
+ "hx-swap": "outerHTML",
51
+ children: [
52
+ /* @__PURE__ */ jsxs("div", {
53
+ class: "field",
54
+ children: [/* @__PURE__ */ jsxs("label", {
55
+ class: "field__label",
56
+ style: "display:flex; gap:.5rem; align-items:center;",
57
+ children: [/* @__PURE__ */ jsx("input", {
58
+ type: "checkbox",
59
+ name: "executePlay",
60
+ value: "true",
61
+ checked: project.executePlay ? true : void 0,
62
+ disabled: !isAdmin
63
+ }), "Enable interaction tests (play)"]
64
+ }), /* @__PURE__ */ jsx("p", {
65
+ class: "field__hint",
66
+ children: "When enabled, failing play blocks the build (failed). Flaky stories show warnings but status stays successful."
67
+ })]
68
+ }),
69
+ /* @__PURE__ */ jsxs("div", {
70
+ class: "field",
71
+ children: [
72
+ /* @__PURE__ */ jsx("label", {
73
+ class: "field__label",
74
+ for: "playTimeoutMs",
75
+ children: "Play timeout (ms)"
76
+ }),
77
+ /* @__PURE__ */ jsx("input", {
78
+ class: "field__input",
79
+ id: "playTimeoutMs",
80
+ name: "playTimeoutMs",
81
+ type: "number",
82
+ min: "1000",
83
+ max: "30000",
84
+ step: "1000",
85
+ value: String(project.playTimeoutMs),
86
+ disabled: !isAdmin
87
+ }),
88
+ /* @__PURE__ */ jsx("p", {
89
+ class: "field__hint",
90
+ children: "Timeout for each play function (1000–30000 ms, default 10000)."
91
+ })
92
+ ]
93
+ }),
94
+ isAdmin ? /* @__PURE__ */ jsx("button", {
95
+ class: "btn btn--primary",
96
+ type: "submit",
97
+ children: "Save changes"
98
+ }) : /* @__PURE__ */ jsx("p", {
99
+ class: "field__hint",
100
+ children: "You need admin access to edit settings."
101
+ })
102
+ ]
103
+ })
104
+ ]
105
+ }),
106
+ /* @__PURE__ */ jsxs("div", {
107
+ class: "card card--padded",
108
+ children: [
109
+ /* @__PURE__ */ jsx("h3", {
110
+ style: "margin:0 0 .4rem;",
111
+ children: "How to mark stories"
112
+ }),
113
+ /* @__PURE__ */ jsx("pre", {
114
+ class: "field__hint",
115
+ style: "white-space:pre-wrap; background: var(--surface); padding:.75rem; border-radius:.5rem;",
116
+ children: `// Disable snapshot entirely (skip capture + play)
117
+ export const Hidden: Story = {
118
+ parameters: { storyshelf: { disableSnapshot: true } }
119
+ };
120
+ // Mark as flaky (non-blocking, shows warning)
121
+ export const Flaky: Story = {
122
+ tags: ['flaky-test'],
123
+ play: async ({canvasElement}) => { /* ... */ }
124
+ };
125
+ // or via parameters (both keys work, storyshelf wins)
126
+ export const Flaky2: Story = {
127
+ parameters: { chromatic: { flakyTest: true } }
128
+ };`
129
+ }),
130
+ /* @__PURE__ */ jsxs("p", {
131
+ class: "field__hint",
132
+ children: [
133
+ "Tags are story-level and case-insensitive (",
134
+ /* @__PURE__ */ jsx("code", { children: "flaky-test" }),
135
+ "). Whole story is non-blocking when flaky. GitHub status stays",
136
+ /* @__PURE__ */ jsx("code", { children: "success" }),
137
+ " with a warning comment."
138
+ ]
139
+ })
140
+ ]
141
+ })
142
+ ]
143
+ });
144
+ }
145
+ //#endregion
146
+ export { renderSettingsTests };
147
+
148
+ //# sourceMappingURL=settings-tests.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-tests.mjs","names":[],"sources":["../../src/pages/settings-tests.tsx"],"sourcesContent":["import type { Project } from \"../schema.ts\";\n\nexport function renderSettingsTests(project: Project, isAdmin: boolean, formState?: { globalError?: string }): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 720px;\">\n {formState?.globalError ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n {formState.globalError}\n </div>\n ) : null}\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .5rem;\">Interaction tests</h2>\n <p class=\"field__hint\" style=\"margin-bottom:1rem;\">\n When enabled, Storybook <code>play</code> functions run before each screenshot. Failures block the build unless the story is marked\n <code>flaky-test</code> via <code>tags: ['flaky-test']</code> or\n <code>parameters: &#123; flakyTest: true &#125;</code> (supports both <code>chromatic</code> and <code>storyshelf</code> keys, story-level, case-insensitive). Use{\" \"}\n <code>disableSnapshot</code> to skip a story entirely.\n </p>\n <form\n method=\"post\"\n action={`/projects/${project.slug}/settings/tests`}\n hx-post={`/projects/${project.slug}/settings/tests`}\n hx-target=\"body\"\n hx-swap=\"outerHTML\"\n >\n <div class=\"field\">\n <label class=\"field__label\" style=\"display:flex; gap:.5rem; align-items:center;\">\n <input type=\"checkbox\" name=\"executePlay\" value=\"true\" checked={project.executePlay ? true : undefined} disabled={!isAdmin} />\n Enable interaction tests (play)\n </label>\n <p class=\"field__hint\">When enabled, failing play blocks the build (failed). Flaky stories show warnings but status stays successful.</p>\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"playTimeoutMs\">\n Play timeout (ms)\n </label>\n <input\n class=\"field__input\"\n id=\"playTimeoutMs\"\n name=\"playTimeoutMs\"\n type=\"number\"\n min=\"1000\"\n max=\"30000\"\n step=\"1000\"\n value={String(project.playTimeoutMs)}\n disabled={!isAdmin}\n />\n <p class=\"field__hint\">Timeout for each play function (1000–30000 ms, default 10000).</p>\n </div>\n {isAdmin ? (\n <button class=\"btn btn--primary\" type=\"submit\">\n Save changes\n </button>\n ) : (\n <p class=\"field__hint\">You need admin access to edit settings.</p>\n )}\n </form>\n </div>\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .4rem;\">How to mark stories</h3>\n <pre class=\"field__hint\" style=\"white-space:pre-wrap; background: var(--surface); padding:.75rem; border-radius:.5rem;\">\n {`// Disable snapshot entirely (skip capture + play)\nexport const Hidden: Story = {\n parameters: { storyshelf: { disableSnapshot: true } }\n};\n// Mark as flaky (non-blocking, shows warning)\nexport const Flaky: Story = {\n tags: ['flaky-test'],\n play: async ({canvasElement}) => { /* ... */ }\n};\n// or via parameters (both keys work, storyshelf wins)\nexport const Flaky2: Story = {\n parameters: { chromatic: { flakyTest: true } }\n};`}\n </pre>\n <p class=\"field__hint\">\n Tags are story-level and case-insensitive (<code>flaky-test</code>). Whole story is non-blocking when flaky. GitHub status stays\n <code>success</code> with a warning comment.\n </p>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAEA,SAAgB,oBAAoB,SAAkB,SAAkB,WAA+C;CACrH,OACE,qBAAC,OAAD;EAAK,OAAM;EAAO,OAAM;EAAxB,UAAA;GACG,WAAW,cACV,oBAAC,OAAD;IAAK,OAAM;IAAsB,MAAK;IACnC,UAAA,UAAU;GACR,CAAA,IACH;GACJ,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA;KACE,oBAAC,MAAD;MAAI,OAAM;MAAoB,UAAA;KAAqB,CAAA;KACnD,qBAAC,KAAD;MAAG,OAAM;MAAc,OAAM;MAA7B,UAAA;OAAmD;OACzB,oBAAC,QAAD,EAAA,UAAM,OAAU,CAAA;OAAC;OACzC,oBAAC,QAAD,EAAA,UAAM,aAAgB,CAAA;OAAC;OAAK,oBAAC,QAAD,EAAA,UAAM,uBAA0B,CAAA;OAAC;OAC7D,oBAAC,QAAD,EAAA,UAAM,kCAA+C,CAAA;OAAC;OAAgB,oBAAC,QAAD,EAAA,UAAM,YAAe,CAAA;OAAC;OAAK,oBAAC,QAAD,EAAA,UAAM,aAAgB,CAAA;OAAC;OAA2C;OACnK,oBAAC,QAAD,EAAA,UAAM,kBAAqB,CAAA;OAAC;MAC3B;;KACH,qBAAC,QAAD;MACE,QAAO;MACP,QAAQ,aAAa,QAAQ,KAAK;MAClC,WAAS,aAAa,QAAQ,KAAK;MACnC,aAAU;MACV,WAAQ;MALV,UAAA;OAOE,qBAAC,OAAD;QAAK,OAAM;QAAX,UAAA,CACE,qBAAC,SAAD;SAAO,OAAM;SAAe,OAAM;SAAlC,UAAA,CACE,oBAAC,SAAD;UAAO,MAAK;UAAW,MAAK;UAAc,OAAM;UAAO,SAAS,QAAQ,cAAc,OAAO,KAAA;UAAW,UAAU,CAAC;SAAU,CAAA,GAAC,iCAEzH;QACP,CAAA,GAAA,oBAAC,KAAD;SAAG,OAAM;SAAc,UAAA;QAAiH,CAAA,CACrI;;OACL,qBAAC,OAAD;QAAK,OAAM;QAAX,UAAA;SACE,oBAAC,SAAD;UAAO,OAAM;UAAe,KAAI;UAAgB,UAAA;SAEzC,CAAA;SACP,oBAAC,SAAD;UACE,OAAM;UACN,IAAG;UACH,MAAK;UACL,MAAK;UACL,KAAI;UACJ,KAAI;UACJ,MAAK;UACL,OAAO,OAAO,QAAQ,aAAa;UACnC,UAAU,CAAC;SACZ,CAAA;SACD,oBAAC,KAAD;UAAG,OAAM;UAAc,UAAA;SAAiE,CAAA;QACrF;;OACJ,UACC,oBAAC,UAAD;QAAQ,OAAM;QAAmB,MAAK;QAAS,UAAA;OAEvC,CAAA,IAER,oBAAC,KAAD;QAAG,OAAM;QAAc,UAAA;OAA0C,CAAA;MAE/D;;IACH;;GACL,qBAAC,OAAD;IAAK,OAAM;IAAX,UAAA;KACE,oBAAC,MAAD;MAAI,OAAM;MAAoB,UAAA;KAAuB,CAAA;KACrD,oBAAC,OAAD;MAAK,OAAM;MAAc,OAAM;MAC5B,UAAA;;;;;;;;;;;;;KAaE,CAAA;KACL,qBAAC,KAAD;MAAG,OAAM;MAAT,UAAA;OAAuB;OACsB,oBAAC,QAAD,EAAA,UAAM,aAAgB,CAAA;OAAC;OAClE,oBAAC,QAAD,EAAA,UAAM,UAAa,CAAA;OAAC;MACnB;;IACA;;EACF;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings-tokens.mjs","names":[],"sources":["../../src/pages/settings-tokens.tsx"],"sourcesContent":["import type { Project, Token } from \"../schema.ts\";\n\nexport function renderSettingsTokens(project: Project, tokens: Array<Omit<Token, \"hash\">>, isAdmin: boolean): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">API tokens</h2>\n <p class=\"field__hint\">Tokens are used by the CLI to upload builds. They are scoped to this project.</p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Created</th>\n <th>Last used</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {tokens.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (token) => (\n <tr key={token.id}>\n <td>{token.name}</td>\n <td>{new Date(token.createdAt).toLocaleDateString()}</td>\n <td>{token.lastUsedAt ? new Date(token.lastUsedAt).toLocaleDateString() : \"—\"}</td>\n <td>\n {isAdmin ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/tokens/${token.id}/delete`} hx-post={`/projects/${project.slug}/settings/tokens/${token.id}/delete`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\">\n Revoke\n </button>\n </form>\n ) : null}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {tokens.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No tokens yet. Create one for CI.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Create token</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/tokens`} hx-post={`/projects/${project.slug}/settings/tokens`} hx-target=\"body\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"tokenName\">\n Name\n </label>\n <input class=\"field__input\" id=\"tokenName\" name=\"tokenName\" required placeholder=\"ci\" />\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Create token\n </button>\n </form>\n <p class=\"field__hint\" style=\"margin-top:.5rem;\">Token value is shown once after creation. Store it securely.</p>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":";;;;;AAEA,SAAgB,qBAAqB,SAAkB,QAAoC,SAA2B;CACpH,OACE,qBAAC,OAAD;EAAK,OAAM;EAAO,OAAM;EAAxB,UAAA,CACE,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAI,OAAM;KAAoB,UAAA;IAAc,CAAA;IAC5C,oBAAC,KAAD;KAAG,OAAM;KAAc,UAAA;IAAgF,CAAA;IACvG,oBAAC,OAAD;KAAK,OAAM;KAAa,OAAM;KAC5B,UAAA,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UAAI,OAAQ,CAAA;MACZ,oBAAC,MAAD,EAAA,UAAI,UAAW,CAAA;MACf,oBAAC,MAAD,EAAA,UAAI,YAAa,CAAA;MACjB,oBAAC,MAAD,CAAQ,CAAA;KACN,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,OAAO,KAEL,UACC,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UAAK,MAAM,KAAS,CAAA;MACpB,oBAAC,MAAD,EAAA,UAAK,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,mBAAmB,EAAM,CAAA;MACxD,oBAAC,MAAD,EAAA,UAAK,MAAM,aAAa,IAAI,KAAK,MAAM,UAAU,CAAC,CAAC,mBAAmB,IAAI,IAAQ,CAAA;MAClF,oBAAC,MAAD,EAAA,UACG,UACC,oBAAC,QAAD;OAAM,QAAO;OAAO,QAAQ,aAAa,QAAQ,KAAK,mBAAmB,MAAM,GAAG;OAAU,WAAS,aAAa,QAAQ,KAAK,mBAAmB,MAAM,GAAG;OAAU,aAAU;OAC7K,UAAA,oBAAC,UAAD;QAAQ,OAAM;QAAiB,MAAK;QAAS,UAAA;OAErC,CAAA;MACJ,CAAA,IACJ,KACF,CAAA;KACF,EAAA,GAbK,MAAM,EAaX,CAER,EACK,CAAA,CACF,EAAA,CAAA;IACJ,CAAA;IACJ,OAAO,WAAW,IAAI,oBAAC,KAAD;KAAG,OAAM;KAAc,OAAM;KAAoB,UAAA;IAAoC,CAAA,IAAI;GAC7G;EAEJ,CAAA,GAAA,UACC,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAI,OAAM;KAAoB,UAAA;IAAgB,CAAA;IAC9C,qBAAC,QAAD;KAAM,QAAO;KAAO,QAAQ,aAAa,QAAQ,KAAK;KAAmB,WAAS,aAAa,QAAQ,KAAK;KAAmB,aAAU;KAAzI,UAAA,CACE,qBAAC,OAAD;MAAK,OAAM;MAAX,UAAA,CACE,oBAAC,SAAD;OAAO,OAAM;OAAe,KAAI;OAAY,UAAA;MAErC,CAAA,GACP,oBAAC,SAAD;OAAO,OAAM;OAAe,IAAG;OAAY,MAAK;OAAY,UAAA;OAAS,aAAY;MAAM,CAAA,CACpF;KACL,CAAA,GAAA,oBAAC,UAAD;MAAQ,OAAM;MAAmB,MAAK;MAAS,UAAA;KAEvC,CAAA,CACJ;;IACN,oBAAC,KAAD;KAAG,OAAM;KAAc,OAAM;KAAoB,UAAA;IAA+D,CAAA;GAC7G;EACH,CAAA,IAAA,IACD;;AAET"}
1
+ {"version":3,"file":"settings-tokens.mjs","names":[],"sources":["../../src/pages/settings-tokens.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { Project, Token } from \"../schema.ts\";\n\nexport function renderSettingsTokens(project: Project, tokens: Omit<Token, \"hash\">[], isAdmin: boolean): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">API tokens</h2>\n <p class=\"field__hint\">Tokens are used by the CLI to upload builds. They are scoped to this project.</p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Created</th>\n <th>Last used</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {tokens.map(\n (token): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <tr key={token.id}>\n <td>{token.name}</td>\n <td>{new Date(token.createdAt).toLocaleDateString()}</td>\n <td>{token.lastUsedAt ? new Date(token.lastUsedAt).toLocaleDateString() : \"—\"}</td>\n <td>\n {isAdmin ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/tokens/${token.id}/delete`} hx-post={`/projects/${project.slug}/settings/tokens/${token.id}/delete`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\">\n Revoke\n </button>\n </form>\n ) : null}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {tokens.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No tokens yet. Create one for CI.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Create token</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/tokens`} hx-post={`/projects/${project.slug}/settings/tokens`} hx-target=\"body\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"tokenName\">\n Name\n </label>\n <input class=\"field__input\" id=\"tokenName\" name=\"tokenName\" required placeholder=\"ci\" />\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Create token\n </button>\n </form>\n <p class=\"field__hint\" style=\"margin-top:.5rem;\">Token value is shown once after creation. Store it securely.</p>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":";;;;;AAGA,SAAgB,qBAAqB,SAAkB,QAA+B,SAA2B;CAC/G,OACE,qBAAC,OAAD;EAAK,OAAM;EAAO,OAAM;EAAxB,UAAA,CACE,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAI,OAAM;KAAoB,UAAA;IAAc,CAAA;IAC5C,oBAAC,KAAD;KAAG,OAAM;KAAc,UAAA;IAAgF,CAAA;IACvG,oBAAC,OAAD;KAAK,OAAM;KAAa,OAAM;KAC5B,UAAA,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UAAI,OAAQ,CAAA;MACZ,oBAAC,MAAD,EAAA,UAAI,UAAW,CAAA;MACf,oBAAC,MAAD,EAAA,UAAI,YAAa,CAAA;MACjB,oBAAC,MAAD,CAAQ,CAAA;KACN,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,OAAO,KACL,UACC,qBAAC,MAAD,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UAAK,MAAM,KAAS,CAAA;MACpB,oBAAC,MAAD,EAAA,UAAK,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,mBAAmB,EAAM,CAAA;MACxD,oBAAC,MAAD,EAAA,UAAK,MAAM,aAAa,IAAI,KAAK,MAAM,UAAU,CAAC,CAAC,mBAAmB,IAAI,IAAQ,CAAA;MAClF,oBAAC,MAAD,EAAA,UACG,UACC,oBAAC,QAAD;OAAM,QAAO;OAAO,QAAQ,aAAa,QAAQ,KAAK,mBAAmB,MAAM,GAAG;OAAU,WAAS,aAAa,QAAQ,KAAK,mBAAmB,MAAM,GAAG;OAAU,aAAU;OAC7K,UAAA,oBAAC,UAAD;QAAQ,OAAM;QAAiB,MAAK;QAAS,UAAA;OAErC,CAAA;MACJ,CAAA,IACJ,KACF,CAAA;KACF,EAAA,GAbK,MAAM,EAaX,CAER,EACK,CAAA,CACF,EAAA,CAAA;IACJ,CAAA;IACJ,OAAO,WAAW,IAAI,oBAAC,KAAD;KAAG,OAAM;KAAc,OAAM;KAAoB,UAAA;IAAoC,CAAA,IAAI;GAC7G;EAEJ,CAAA,GAAA,UACC,qBAAC,OAAD;GAAK,OAAM;GAAX,UAAA;IACE,oBAAC,MAAD;KAAI,OAAM;KAAoB,UAAA;IAAgB,CAAA;IAC9C,qBAAC,QAAD;KAAM,QAAO;KAAO,QAAQ,aAAa,QAAQ,KAAK;KAAmB,WAAS,aAAa,QAAQ,KAAK;KAAmB,aAAU;KAAzI,UAAA,CACE,qBAAC,OAAD;MAAK,OAAM;MAAX,UAAA,CACE,oBAAC,SAAD;OAAO,OAAM;OAAe,KAAI;OAAY,UAAA;MAErC,CAAA,GACP,oBAAC,SAAD;OAAO,OAAM;OAAe,IAAG;OAAY,MAAK;OAAY,UAAA;OAAS,aAAY;MAAM,CAAA,CACpF;KACL,CAAA,GAAA,oBAAC,UAAD;MAAQ,OAAM;MAAmB,MAAK;MAAS,UAAA;KAEvC,CAAA,CACJ;;IACN,oBAAC,KAAD;KAAG,OAAM;KAAc,OAAM;KAAoB,UAAA;IAA+D,CAAA;GAC7G;EACH,CAAA,IAAA,IACD;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings-webhooks.mjs","names":[],"sources":["../../src/pages/settings-webhooks.tsx"],"sourcesContent":["import type { Project } from \"../schema.ts\";\n\nexport interface SettingsWebhook {\n id: string;\n url: string;\n events: string[];\n}\n\nexport function renderSettingsWebhooks(project: Project, webhooks: SettingsWebhook[], isAdmin: boolean, formState?: { errors?: Record<string, string>; globalError?: string; secret?: string }): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n {formState?.secret ? (\n <div class=\"alert alert--success\" role=\"alert\">\n <strong class=\"alert__title\">Webhook created</strong>\n <div class=\"alert__body\">\n Copy the signing secret now — shown once: <code>{formState.secret}</code>\n </div>\n </div>\n ) : null}\n {formState?.globalError ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n {formState.globalError}\n </div>\n ) : null}\n\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">Webhooks</h2>\n <p class=\"field__hint\">\n Notify external services when builds are created, updated, approved or rejected. Payloads are POSTed as JSON and signed with the webhook secret.\n </p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>URL</th>\n <th>Events</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {webhooks.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (webhook) => (\n <tr key={webhook.id}>\n <td style=\"max-width:36ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\" title={webhook.url}>\n {webhook.url}\n </td>\n <td>\n {webhook.events.length === 0 ? (\n <span class=\"field__hint\">all events</span>\n ) : (\n <div style=\"display:flex; gap:.25rem; flex-wrap:wrap;\">\n {webhook.events.map(\n // eslint-disable-next-line promise-function-async -- JSX.Element includes Promise<HtmlEscapedString>\n (event) => (\n <span key={event} class=\"badge badge--neutral\">\n {event}\n </span>\n ),\n )}\n </div>\n )}\n </td>\n <td>\n {isAdmin ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/webhooks/${webhook.id}/delete`} hx-post={`/projects/${project.slug}/settings/webhooks/${webhook.id}/delete`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\">\n Delete\n </button>\n </form>\n ) : null}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {webhooks.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No webhooks configured.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Create webhook</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/webhooks`} hx-post={`/projects/${project.slug}/settings/webhooks`} hx-target=\"body\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"url\">\n URL\n </label>\n <input class={`field__input ${formState?.errors?.[\"url\"] ? \"field__input--error\" : \"\"}`} id=\"url\" name=\"url\" type=\"url\" required placeholder=\"https://example.com/hooks/storyshelf\" />\n {formState?.errors?.[\"url\"] ? (\n <p class=\"field__error\" role=\"alert\">\n {formState.errors[\"url\"]}\n </p>\n ) : null}\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"events\">\n Events\n </label>\n <input class=\"field__input\" id=\"events\" name=\"events\" placeholder=\"build.created, build.approved, snapshot.reviewed\" />\n <p class=\"field__hint\">Comma-separated. Leave blank to receive all events.</p>\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Add webhook\n </button>\n </form>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":"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"}
1
+ {"version":3,"file":"settings-webhooks.mjs","names":[],"sources":["../../src/pages/settings-webhooks.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { Project } from \"../schema.ts\";\n\nexport interface SettingsWebhook {\n id: string;\n url: string;\n events: string[];\n}\n\nexport function renderSettingsWebhooks(project: Project, webhooks: SettingsWebhook[], isAdmin: boolean, formState?: { errors?: Record<string, string>; globalError?: string; secret?: string }): unknown {\n return (\n <div class=\"grid\" style=\"max-width: 880px;\">\n {formState?.secret ? (\n <div class=\"alert alert--success\" role=\"alert\">\n <strong class=\"alert__title\">Webhook created</strong>\n <div class=\"alert__body\">\n Copy the signing secret now — shown once: <code>{formState.secret}</code>\n </div>\n </div>\n ) : null}\n {formState?.globalError ? (\n <div class=\"alert alert--danger\" role=\"alert\">\n {formState.globalError}\n </div>\n ) : null}\n\n <div class=\"card card--padded\">\n <h2 style=\"margin:0 0 .3rem;\">Webhooks</h2>\n <p class=\"field__hint\">\n Notify external services when builds are created, updated, approved or rejected. Payloads are POSTed as JSON and signed with the webhook secret.\n </p>\n <div class=\"table-wrap\" style=\"margin-top:.75rem;\">\n <table>\n <thead>\n <tr>\n <th>URL</th>\n <th>Events</th>\n <th></th>\n </tr>\n </thead>\n <tbody>\n {webhooks.map(\n (webhook): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <tr key={webhook.id}>\n <td style=\"max-width:36ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\" title={webhook.url}>\n {webhook.url}\n </td>\n <td>\n {webhook.events.length === 0 ? (\n <span class=\"field__hint\">all events</span>\n ) : (\n <div style=\"display:flex; gap:.25rem; flex-wrap:wrap;\">\n {webhook.events.map(\n (event): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <span key={event} class=\"badge badge--neutral\">\n {event}\n </span>\n ),\n )}\n </div>\n )}\n </td>\n <td>\n {isAdmin ? (\n <form method=\"post\" action={`/projects/${project.slug}/settings/webhooks/${webhook.id}/delete`} hx-post={`/projects/${project.slug}/settings/webhooks/${webhook.id}/delete`} hx-target=\"body\">\n <button class=\"btn btn--ghost\" type=\"submit\">\n Delete\n </button>\n </form>\n ) : null}\n </td>\n </tr>\n ),\n )}\n </tbody>\n </table>\n </div>\n {webhooks.length === 0 ? <p class=\"field__hint\" style=\"margin-top:.5rem;\">No webhooks configured.</p> : null}\n </div>\n\n {isAdmin ? (\n <div class=\"card card--padded\">\n <h3 style=\"margin:0 0 .5rem;\">Create webhook</h3>\n <form method=\"post\" action={`/projects/${project.slug}/settings/webhooks`} hx-post={`/projects/${project.slug}/settings/webhooks`} hx-target=\"body\">\n <div class=\"field\">\n <label class=\"field__label\" for=\"url\">\n URL\n </label>\n <input class={`field__input ${formState?.errors?.[\"url\"] ? \"field__input--error\" : \"\"}`} id=\"url\" name=\"url\" type=\"url\" required placeholder=\"https://example.com/hooks/storyshelf\" />\n {formState?.errors?.[\"url\"] ? (\n <p class=\"field__error\" role=\"alert\">\n {formState.errors[\"url\"]}\n </p>\n ) : null}\n </div>\n <div class=\"field\">\n <label class=\"field__label\" for=\"events\">\n Events\n </label>\n <input class=\"field__input\" id=\"events\" name=\"events\" placeholder=\"build.created, build.approved, snapshot.reviewed\" />\n <p class=\"field__hint\">Comma-separated. Leave blank to receive all events.</p>\n </div>\n <button class=\"btn btn--primary\" type=\"submit\">\n Add webhook\n </button>\n </form>\n </div>\n ) : null}\n </div>\n );\n}"],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getStore } from "../store.mjs";
5
+ //#region src/pages/storybook.tsx
6
+ /**
7
+ * Landing page for a published Storybook build. Presents the live Storybook
8
+ * (via an iframe against the same build's statics) with the project chrome, so
9
+ * the URL is shareable with designers/managers (ADR 0011).
10
+ */
11
+ function renderStorybookPage(project, build, slug) {
12
+ const { ui } = getStore();
13
+ const name = ui.name ?? "StoryShelf";
14
+ return `<!doctype html>
15
+ <html lang="en" data-theme="system">
16
+ <head>
17
+ <meta charset="utf-8" />
18
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
19
+ <title>${escapeHtml(project.name)} Storybook · ${name}</title>
20
+ <style>
21
+ html, body { margin: 0; height: 100%; display: flex; flex-direction: column; font-family: system-ui, sans-serif; }
22
+ header { display: flex; align-items: center; gap: .75rem; padding: .5rem 1rem; border-bottom: 1px solid var(--border, #ddd); }
23
+ header a { color: inherit; text-decoration: none; }
24
+ header .crumb { opacity: .7; }
25
+ header .branch { font-weight: 600; }
26
+ iframe { flex: 1; border: 0; width: 100%; }
27
+ </style>
28
+ </head>
29
+ <body>
30
+ <header>
31
+ <a href="/">${name}</a>
32
+ <span class="crumb">/</span>
33
+ <a href="/projects/${escapeAttr(slug)}/builds">${escapeHtml(project.name)}</a>
34
+ <span class="crumb">/</span>
35
+ <span class="branch">${escapeHtml(build.gitBranch)}</span>
36
+ <span class="crumb">· ${build.gitSha.slice(0, 7)}</span>
37
+ </header>
38
+ <iframe src="${escapeAttr(`./iframe.html`)}" title="${escapeAttr(project.name)} Storybook"></iframe>
39
+ </body>
40
+ </html>`;
41
+ }
42
+ function escapeHtml(value) {
43
+ return value.replaceAll(/[&<>"']/gu, (ch) => {
44
+ switch (ch) {
45
+ case "&": return "&amp;";
46
+ case "<": return "&lt;";
47
+ case ">": return "&gt;";
48
+ case "\"": return "&quot;";
49
+ default: return "&#39;";
50
+ }
51
+ });
52
+ }
53
+ function escapeAttr(value) {
54
+ return escapeHtml(value);
55
+ }
56
+ //#endregion
57
+ export { renderStorybookPage };
58
+
59
+ //# sourceMappingURL=storybook.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/pages/storybook.tsx"],"sourcesContent":["import { getStore } from \"../store.ts\";\nimport type { Build, Project } from \"../schema.ts\";\n\n/**\n * Landing page for a published Storybook build. Presents the live Storybook\n * (via an iframe against the same build's statics) with the project chrome, so\n * the URL is shareable with designers/managers (ADR 0011).\n */\nexport function renderStorybookPage(project: Project, build: Build, slug: string): string {\n const { ui } = getStore();\n const name = ui.name ?? \"StoryShelf\";\n const iframeSrc = `./iframe.html`;\n return `<!doctype html>\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 <title>${escapeHtml(project.name)} Storybook · ${name}</title>\n <style>\n html, body { margin: 0; height: 100%; display: flex; flex-direction: column; font-family: system-ui, sans-serif; }\n header { display: flex; align-items: center; gap: .75rem; padding: .5rem 1rem; border-bottom: 1px solid var(--border, #ddd); }\n header a { color: inherit; text-decoration: none; }\n header .crumb { opacity: .7; }\n header .branch { font-weight: 600; }\n iframe { flex: 1; border: 0; width: 100%; }\n </style>\n </head>\n <body>\n <header>\n <a href=\"/\">${name}</a>\n <span class=\"crumb\">/</span>\n <a href=\"/projects/${escapeAttr(slug)}/builds\">${escapeHtml(project.name)}</a>\n <span class=\"crumb\">/</span>\n <span class=\"branch\">${escapeHtml(build.gitBranch)}</span>\n <span class=\"crumb\">· ${build.gitSha.slice(0, 7)}</span>\n </header>\n <iframe src=\"${escapeAttr(iframeSrc)}\" title=\"${escapeAttr(project.name)} Storybook\"></iframe>\n </body>\n</html>`;\n}\n\nfunction escapeHtml(value: string): string {\n return value.replaceAll(/[&<>\"']/gu, (ch) => {\n switch (ch) {\n case \"&\": {\n return \"&amp;\";\n }\n case \"<\": {\n return \"&lt;\";\n }\n case \">\": {\n return \"&gt;\";\n }\n case '\"': {\n return \"&quot;\";\n }\n default: {\n return \"&#39;\";\n }\n }\n });\n}\n\nfunction escapeAttr(value: string): string {\n return escapeHtml(value);\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,oBAAoB,SAAkB,OAAc,MAAsB;CACxF,MAAM,EAAE,OAAO,SAAS;CACxB,MAAM,OAAO,GAAG,QAAQ;CAExB,OAAO;;;;;aAKI,WAAW,QAAQ,IAAI,EAAE,eAAe,KAAK;;;;;;;;;;;;oBAYtC,KAAK;;2BAEE,WAAW,IAAI,EAAE,WAAW,WAAW,QAAQ,IAAI,EAAE;;6BAEnD,WAAW,MAAM,SAAS,EAAE;8BAC3B,MAAM,OAAO,MAAM,GAAG,CAAC,EAAE;;mBAEpC,WAAW,eAAS,EAAE,WAAW,WAAW,QAAQ,IAAI,EAAE;;;AAG7E;AAEA,SAAS,WAAW,OAAuB;CACzC,OAAO,MAAM,WAAW,cAAc,OAAO;EAC3C,QAAQ,IAAR;GACE,KAAK,KACH,OAAO;GAET,KAAK,KACH,OAAO;GAET,KAAK,KACH,OAAO;GAET,KAAK,MACH,OAAO;GAET,SACE,OAAO;EAEX;CACF,CAAC;AACH;AAEA,SAAS,WAAW,OAAuB;CACzC,OAAO,WAAW,KAAK;AACzB"}
@@ -36,7 +36,9 @@ const buildUploadSchema = z.object({
36
36
  }).openapi("BuildUpload");
37
37
  const buildListQuery = z.object({
38
38
  status: z.enum(BUILD_STATUSES).optional(),
39
- branch: z.string().optional()
39
+ branch: z.string().optional(),
40
+ labelKey: z.string().optional(),
41
+ labelValue: z.string().optional()
40
42
  });
41
43
  async function buildForProject(projectId, buildId) {
42
44
  const build = await new BuildModel(getStore().db).get(buildId);
@@ -49,7 +51,7 @@ async function snapshotForBuild(build, snapshotId) {
49
51
  return snapshot;
50
52
  }
51
53
  async function refreshBuild(buildId) {
52
- const db = getStore().db;
54
+ const { db } = getStore();
53
55
  await new BuildModel(db).updateCounts(buildId);
54
56
  const snapshots = await new SnapshotModel(db).listByBuild(buildId);
55
57
  const unresolved = snapshots.some((s) => s.status === "new" || s.status === "changed");
@@ -67,7 +69,7 @@ async function refreshBuild(buildId) {
67
69
  }
68
70
  }
69
71
  async function approveSnapshot(snapshotId, userId) {
70
- const db = getStore().db;
72
+ const { db } = getStore();
71
73
  const snapshots = new SnapshotModel(db);
72
74
  const snapshot = await snapshots.get(snapshotId);
73
75
  if (!snapshot) notFound("Snapshot not found");