@storyshelf/app 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/app-types.d.mts +54 -0
  4. package/dist/asset-manifest.mjs +41 -0
  5. package/dist/asset-manifest.mjs.map +1 -0
  6. package/dist/asset-paths.mjs +23 -0
  7. package/dist/asset-paths.mjs.map +1 -0
  8. package/dist/assets/htmx.min.js +3304 -0
  9. package/dist/assets/mark.svg +1 -0
  10. package/dist/capture-setup.mjs +139 -0
  11. package/dist/capture-setup.mjs.map +1 -0
  12. package/dist/credential-keys.mjs +20 -0
  13. package/dist/credential-keys.mjs.map +1 -0
  14. package/dist/credentials.mjs +56 -0
  15. package/dist/credentials.mjs.map +1 -0
  16. package/dist/index.d.mts +18 -0
  17. package/dist/index.mjs +310 -0
  18. package/dist/index.mjs.map +1 -0
  19. package/dist/lifecycle.mjs +132 -0
  20. package/dist/lifecycle.mjs.map +1 -0
  21. package/dist/middleware/auth-gate.mjs +36 -0
  22. package/dist/middleware/auth-gate.mjs.map +1 -0
  23. package/dist/middleware/csrf.mjs +75 -0
  24. package/dist/middleware/csrf.mjs.map +1 -0
  25. package/dist/middleware/rate-limit.mjs +68 -0
  26. package/dist/middleware/rate-limit.mjs.map +1 -0
  27. package/dist/middleware/request-log.mjs +30 -0
  28. package/dist/middleware/request-log.mjs.map +1 -0
  29. package/dist/middleware/server-timing.mjs +39 -0
  30. package/dist/middleware/server-timing.mjs.map +1 -0
  31. package/dist/middleware/setup-gate.mjs +37 -0
  32. package/dist/middleware/setup-gate.mjs.map +1 -0
  33. package/dist/middleware/store-scope.mjs +33 -0
  34. package/dist/middleware/store-scope.mjs.map +1 -0
  35. package/dist/notify.d.mts +17 -0
  36. package/dist/notify.mjs +141 -0
  37. package/dist/notify.mjs.map +1 -0
  38. package/dist/pages/admin-system.mjs +243 -0
  39. package/dist/pages/admin-system.mjs.map +1 -0
  40. package/dist/pages/build-detail.mjs +392 -0
  41. package/dist/pages/build-detail.mjs.map +1 -0
  42. package/dist/pages/build-diff-actions.mjs +162 -0
  43. package/dist/pages/build-diff-actions.mjs.map +1 -0
  44. package/dist/pages/build-diff-comments.mjs +104 -0
  45. package/dist/pages/build-diff-comments.mjs.map +1 -0
  46. package/dist/pages/build-diff-header.mjs +117 -0
  47. package/dist/pages/build-diff-header.mjs.map +1 -0
  48. package/dist/pages/build-diff-model.mjs +48 -0
  49. package/dist/pages/build-diff-model.mjs.map +1 -0
  50. package/dist/pages/build-diff-nav.mjs +122 -0
  51. package/dist/pages/build-diff-nav.mjs.map +1 -0
  52. package/dist/pages/build-diff-shortcuts.mjs +78 -0
  53. package/dist/pages/build-diff-shortcuts.mjs.map +1 -0
  54. package/dist/pages/build-diff-stale.mjs +70 -0
  55. package/dist/pages/build-diff-stale.mjs.map +1 -0
  56. package/dist/pages/build-diff-viewer.mjs +70 -0
  57. package/dist/pages/build-diff-viewer.mjs.map +1 -0
  58. package/dist/pages/build-diff.mjs +110 -0
  59. package/dist/pages/build-diff.mjs.map +1 -0
  60. package/dist/pages/compute-jobs.mjs +165 -0
  61. package/dist/pages/compute-jobs.mjs.map +1 -0
  62. package/dist/pages/invite.mjs +91 -0
  63. package/dist/pages/invite.mjs.map +1 -0
  64. package/dist/pages/label-detail.mjs +154 -0
  65. package/dist/pages/label-detail.mjs.map +1 -0
  66. package/dist/pages/library.mjs +343 -0
  67. package/dist/pages/library.mjs.map +1 -0
  68. package/dist/pages/login.mjs +117 -0
  69. package/dist/pages/login.mjs.map +1 -0
  70. package/dist/pages/passkey-ceremony.mjs +97 -0
  71. package/dist/pages/passkey-ceremony.mjs.map +1 -0
  72. package/dist/pages/profile.mjs +364 -0
  73. package/dist/pages/profile.mjs.map +1 -0
  74. package/dist/pages/project-builds-table.mjs +151 -0
  75. package/dist/pages/project-builds-table.mjs.map +1 -0
  76. package/dist/pages/project-builds.mjs +143 -0
  77. package/dist/pages/project-builds.mjs.map +1 -0
  78. package/dist/pages/project-create.mjs +90 -0
  79. package/dist/pages/project-create.mjs.map +1 -0
  80. package/dist/pages/project-settings.mjs +95 -0
  81. package/dist/pages/project-settings.mjs.map +1 -0
  82. package/dist/pages/projects-card.mjs +194 -0
  83. package/dist/pages/projects-card.mjs.map +1 -0
  84. package/dist/pages/projects.mjs +129 -0
  85. package/dist/pages/projects.mjs.map +1 -0
  86. package/dist/pages/root.mjs +98 -0
  87. package/dist/pages/root.mjs.map +1 -0
  88. package/dist/pages/settings-general.mjs +138 -0
  89. package/dist/pages/settings-general.mjs.map +1 -0
  90. package/dist/pages/settings-labels.mjs +107 -0
  91. package/dist/pages/settings-labels.mjs.map +1 -0
  92. package/dist/pages/settings-members.mjs +192 -0
  93. package/dist/pages/settings-members.mjs.map +1 -0
  94. package/dist/pages/settings-notifications.mjs +240 -0
  95. package/dist/pages/settings-notifications.mjs.map +1 -0
  96. package/dist/pages/settings-status.mjs +137 -0
  97. package/dist/pages/settings-status.mjs.map +1 -0
  98. package/dist/pages/settings-tests.mjs +111 -0
  99. package/dist/pages/settings-tests.mjs.map +1 -0
  100. package/dist/pages/settings-tokens.mjs +90 -0
  101. package/dist/pages/settings-tokens.mjs.map +1 -0
  102. package/dist/pages/settings-webhooks.mjs +104 -0
  103. package/dist/pages/settings-webhooks.mjs.map +1 -0
  104. package/dist/pages/storybook.mjs +116 -0
  105. package/dist/pages/storybook.mjs.map +1 -0
  106. package/dist/retention-timer.mjs +65 -0
  107. package/dist/retention-timer.mjs.map +1 -0
  108. package/dist/routers/admin-pages.mjs +48 -0
  109. package/dist/routers/admin-pages.mjs.map +1 -0
  110. package/dist/routers/admin.mjs +113 -0
  111. package/dist/routers/admin.mjs.map +1 -0
  112. package/dist/routers/assets.mjs +64 -0
  113. package/dist/routers/assets.mjs.map +1 -0
  114. package/dist/routers/attempts.mjs +96 -0
  115. package/dist/routers/attempts.mjs.map +1 -0
  116. package/dist/routers/auth-sync.mjs +64 -0
  117. package/dist/routers/auth-sync.mjs.map +1 -0
  118. package/dist/routers/auth.mjs +236 -0
  119. package/dist/routers/auth.mjs.map +1 -0
  120. package/dist/routers/baseline-guard.mjs +83 -0
  121. package/dist/routers/baseline-guard.mjs.map +1 -0
  122. package/dist/routers/builds.handlers.mjs +217 -0
  123. package/dist/routers/builds.handlers.mjs.map +1 -0
  124. package/dist/routers/builds.mjs +381 -0
  125. package/dist/routers/builds.mjs.map +1 -0
  126. package/dist/routers/comments.mjs +109 -0
  127. package/dist/routers/comments.mjs.map +1 -0
  128. package/dist/routers/flash.mjs +35 -0
  129. package/dist/routers/flash.mjs.map +1 -0
  130. package/dist/routers/health-report.mjs +159 -0
  131. package/dist/routers/health-report.mjs.map +1 -0
  132. package/dist/routers/health.mjs +46 -0
  133. package/dist/routers/health.mjs.map +1 -0
  134. package/dist/routers/helpers.mjs +110 -0
  135. package/dist/routers/helpers.mjs.map +1 -0
  136. package/dist/routers/htmx.mjs +24 -0
  137. package/dist/routers/htmx.mjs.map +1 -0
  138. package/dist/routers/labels.mjs +118 -0
  139. package/dist/routers/labels.mjs.map +1 -0
  140. package/dist/routers/media.mjs +53 -0
  141. package/dist/routers/media.mjs.map +1 -0
  142. package/dist/routers/members.mjs +177 -0
  143. package/dist/routers/members.mjs.map +1 -0
  144. package/dist/routers/notification-channels.mjs +190 -0
  145. package/dist/routers/notification-channels.mjs.map +1 -0
  146. package/dist/routers/notification-subscriptions.mjs +120 -0
  147. package/dist/routers/notification-subscriptions.mjs.map +1 -0
  148. package/dist/routers/profile.mjs +276 -0
  149. package/dist/routers/profile.mjs.map +1 -0
  150. package/dist/routers/projects.mjs +133 -0
  151. package/dist/routers/projects.mjs.map +1 -0
  152. package/dist/routers/review-next.mjs +47 -0
  153. package/dist/routers/review-next.mjs.map +1 -0
  154. package/dist/routers/schemas.mjs +337 -0
  155. package/dist/routers/schemas.mjs.map +1 -0
  156. package/dist/routers/settings-general.mjs +104 -0
  157. package/dist/routers/settings-general.mjs.map +1 -0
  158. package/dist/routers/settings-labels.mjs +52 -0
  159. package/dist/routers/settings-labels.mjs.map +1 -0
  160. package/dist/routers/settings-members.mjs +58 -0
  161. package/dist/routers/settings-members.mjs.map +1 -0
  162. package/dist/routers/settings-notifications.mjs +108 -0
  163. package/dist/routers/settings-notifications.mjs.map +1 -0
  164. package/dist/routers/settings-status.mjs +77 -0
  165. package/dist/routers/settings-status.mjs.map +1 -0
  166. package/dist/routers/settings-tokens.mjs +40 -0
  167. package/dist/routers/settings-tokens.mjs.map +1 -0
  168. package/dist/routers/settings-webhooks.mjs +67 -0
  169. package/dist/routers/settings-webhooks.mjs.map +1 -0
  170. package/dist/routers/settings.handlers.mjs +149 -0
  171. package/dist/routers/settings.handlers.mjs.map +1 -0
  172. package/dist/routers/settings.mjs +25 -0
  173. package/dist/routers/settings.mjs.map +1 -0
  174. package/dist/routers/snapshots.mjs +240 -0
  175. package/dist/routers/snapshots.mjs.map +1 -0
  176. package/dist/routers/status-configs.mjs +108 -0
  177. package/dist/routers/status-configs.mjs.map +1 -0
  178. package/dist/routers/storybook.mjs +171 -0
  179. package/dist/routers/storybook.mjs.map +1 -0
  180. package/dist/routers/tokens.mjs +91 -0
  181. package/dist/routers/tokens.mjs.map +1 -0
  182. package/dist/routers/ui.mjs +166 -0
  183. package/dist/routers/ui.mjs.map +1 -0
  184. package/dist/routers/webhooks.mjs +97 -0
  185. package/dist/routers/webhooks.mjs.map +1 -0
  186. package/dist/routers/well-known.mjs +43 -0
  187. package/dist/routers/well-known.mjs.map +1 -0
  188. package/dist/runtime.mjs +52 -0
  189. package/dist/runtime.mjs.map +1 -0
  190. package/dist/store.mjs +20 -0
  191. package/dist/store.mjs.map +1 -0
  192. package/dist/ui/avatar.mjs +76 -0
  193. package/dist/ui/avatar.mjs.map +1 -0
  194. package/dist/ui/buttons.mjs +205 -0
  195. package/dist/ui/buttons.mjs.map +1 -0
  196. package/dist/ui/code-block.mjs +47 -0
  197. package/dist/ui/code-block.mjs.map +1 -0
  198. package/dist/ui/compare-stage.mjs +344 -0
  199. package/dist/ui/compare-stage.mjs.map +1 -0
  200. package/dist/ui/csrf-field.mjs +20 -0
  201. package/dist/ui/csrf-field.mjs.map +1 -0
  202. package/dist/ui/css.mjs +36 -0
  203. package/dist/ui/css.mjs.map +1 -0
  204. package/dist/ui/document.mjs +130 -0
  205. package/dist/ui/document.mjs.map +1 -0
  206. package/dist/ui/dropdown.mjs +181 -0
  207. package/dist/ui/dropdown.mjs.map +1 -0
  208. package/dist/ui/feedback.mjs +341 -0
  209. package/dist/ui/feedback.mjs.map +1 -0
  210. package/dist/ui/filter-input.mjs +58 -0
  211. package/dist/ui/filter-input.mjs.map +1 -0
  212. package/dist/ui/forms.mjs +291 -0
  213. package/dist/ui/forms.mjs.map +1 -0
  214. package/dist/ui/icons/icon.mjs +53 -0
  215. package/dist/ui/icons/icon.mjs.map +1 -0
  216. package/dist/ui/icons/paths.mjs +61 -0
  217. package/dist/ui/icons/paths.mjs.map +1 -0
  218. package/dist/ui/icons/sprite.mjs +26 -0
  219. package/dist/ui/icons/sprite.mjs.map +1 -0
  220. package/dist/ui/kbd.mjs +33 -0
  221. package/dist/ui/kbd.mjs.map +1 -0
  222. package/dist/ui/layout.mjs +173 -0
  223. package/dist/ui/layout.mjs.map +1 -0
  224. package/dist/ui/progress.mjs +44 -0
  225. package/dist/ui/progress.mjs.map +1 -0
  226. package/dist/ui/segmented.mjs +77 -0
  227. package/dist/ui/segmented.mjs.map +1 -0
  228. package/dist/ui/shell/account.mjs +161 -0
  229. package/dist/ui/shell/account.mjs.map +1 -0
  230. package/dist/ui/shell/bare.mjs +69 -0
  231. package/dist/ui/shell/bare.mjs.map +1 -0
  232. package/dist/ui/shell/boot-script.mjs +46 -0
  233. package/dist/ui/shell/boot-script.mjs.map +1 -0
  234. package/dist/ui/shell/client-script.mjs +259 -0
  235. package/dist/ui/shell/client-script.mjs.map +1 -0
  236. package/dist/ui/shell/nav.mjs +111 -0
  237. package/dist/ui/shell/nav.mjs.map +1 -0
  238. package/dist/ui/shell/review-script.mjs +195 -0
  239. package/dist/ui/shell/review-script.mjs.map +1 -0
  240. package/dist/ui/shell/sidebar.mjs +323 -0
  241. package/dist/ui/shell/sidebar.mjs.map +1 -0
  242. package/dist/ui/shell/topbar.mjs +268 -0
  243. package/dist/ui/shell/topbar.mjs.map +1 -0
  244. package/dist/ui/stacks.mjs +82 -0
  245. package/dist/ui/stacks.mjs.map +1 -0
  246. package/dist/ui/styles/base.mjs +41 -0
  247. package/dist/ui/styles/base.mjs.map +1 -0
  248. package/dist/ui/styles/behavior.mjs +43 -0
  249. package/dist/ui/styles/behavior.mjs.map +1 -0
  250. package/dist/ui/styles/review-layout.mjs +347 -0
  251. package/dist/ui/styles/review-layout.mjs.map +1 -0
  252. package/dist/ui/styles/review.mjs +86 -0
  253. package/dist/ui/styles/review.mjs.map +1 -0
  254. package/dist/ui/styles/tokens.mjs +132 -0
  255. package/dist/ui/styles/tokens.mjs.map +1 -0
  256. package/dist/ui/styles.mjs +19 -0
  257. package/dist/ui/styles.mjs.map +1 -0
  258. package/dist/ui/subnav.mjs +91 -0
  259. package/dist/ui/subnav.mjs.map +1 -0
  260. package/dist/ui/table.mjs +73 -0
  261. package/dist/ui/table.mjs.map +1 -0
  262. package/dist/ui/theme.mjs +64 -0
  263. package/dist/ui/theme.mjs.map +1 -0
  264. package/dist/ui/thumbnail.mjs +51 -0
  265. package/dist/ui/thumbnail.mjs.map +1 -0
  266. package/dist/ui/time.mjs +50 -0
  267. package/dist/ui/time.mjs.map +1 -0
  268. package/package.json +92 -0
@@ -0,0 +1,341 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { css } from "./css.mjs";
5
+ import { Icon } from "./icons/icon.mjs";
6
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
7
+ //#region src/ui/feedback.tsx
8
+ const badgeBase = css`
9
+ /* badge */
10
+ display: inline-flex;
11
+ align-items: center;
12
+ gap: 0.3rem;
13
+ padding: 0.1rem 0.55rem;
14
+ border-radius: var(--radius-pill);
15
+ font-size: var(--text-xs);
16
+ font-weight: 600;
17
+ line-height: 1.5;
18
+ border: 1px solid var(--border);
19
+ background: var(--surface-muted);
20
+ color: var(--text-secondary);
21
+ white-space: nowrap;
22
+ `;
23
+ const badgeTones = {
24
+ neutral: css`
25
+ /* badge-neutral */
26
+ ${badgeBase}
27
+ `,
28
+ success: css`
29
+ /* badge-success */
30
+ ${badgeBase}
31
+ background: var(--status-approved-bg);
32
+ color: var(--status-approved-fg);
33
+ border-color: var(--status-approved-border);
34
+ `,
35
+ warning: css`
36
+ /* badge-warning */
37
+ ${badgeBase}
38
+ background: var(--status-new-bg);
39
+ color: var(--status-new-fg);
40
+ border-color: var(--status-new-border);
41
+ `,
42
+ danger: css`
43
+ /* badge-danger */
44
+ ${badgeBase}
45
+ background: var(--status-rejected-bg);
46
+ color: var(--status-rejected-fg);
47
+ border-color: var(--status-rejected-border);
48
+ `,
49
+ info: css`
50
+ /* badge-info */
51
+ ${badgeBase}
52
+ background: var(--status-info-bg);
53
+ color: var(--accent-fg);
54
+ border-color: var(--status-info-border);
55
+ `
56
+ };
57
+ /** Status glyph per tone so state never relies on color alone. */
58
+ const toneIcons = {
59
+ neutral: "info",
60
+ success: "check-circle",
61
+ warning: "alert-triangle",
62
+ danger: "x-circle",
63
+ info: "info"
64
+ };
65
+ const alertBase = css`
66
+ /* alert */
67
+ display: flex;
68
+ gap: 0.6rem;
69
+ align-items: flex-start;
70
+ border-radius: var(--radius);
71
+ padding: 0.75rem 0.9rem;
72
+ border: 1px solid var(--border);
73
+ background: var(--surface-card);
74
+ font-size: var(--text-base);
75
+ `;
76
+ const alertTones = {
77
+ neutral: css`
78
+ /* alert-neutral */
79
+ ${alertBase}
80
+ `,
81
+ info: css`
82
+ /* alert-info */
83
+ ${alertBase}
84
+ border-color: var(--status-info-border);
85
+ background: var(--status-info-bg);
86
+ `,
87
+ success: css`
88
+ /* alert-success */
89
+ ${alertBase}
90
+ border-color: var(--status-approved-border);
91
+ background: var(--status-approved-bg);
92
+ `,
93
+ warning: css`
94
+ /* alert-warning */
95
+ ${alertBase}
96
+ border-color: var(--status-new-border);
97
+ background: var(--status-new-bg);
98
+ `,
99
+ danger: css`
100
+ /* alert-danger */
101
+ ${alertBase}
102
+ border-color: var(--status-rejected-border);
103
+ background: var(--status-rejected-bg);
104
+ `
105
+ };
106
+ const alertIconTones = {
107
+ neutral: css`
108
+ /* alert-icon-neutral */
109
+ margin-top: 0.1rem;
110
+ color: var(--text-secondary);
111
+ `,
112
+ info: css`
113
+ /* alert-icon-info */
114
+ margin-top: 0.1rem;
115
+ color: var(--accent-fg);
116
+ `,
117
+ success: css`
118
+ /* alert-icon-success */
119
+ margin-top: 0.1rem;
120
+ color: var(--status-approved);
121
+ `,
122
+ warning: css`
123
+ /* alert-icon-warning */
124
+ margin-top: 0.1rem;
125
+ color: var(--status-new);
126
+ `,
127
+ danger: css`
128
+ /* alert-icon-danger */
129
+ margin-top: 0.1rem;
130
+ color: var(--status-rejected);
131
+ `
132
+ };
133
+ const emptyWrap = css`
134
+ /* empty */
135
+ text-align: center;
136
+ padding: 2rem 1rem;
137
+ border: 1px dashed var(--border);
138
+ border-radius: var(--radius);
139
+ background: var(--surface-card);
140
+ `;
141
+ const emptyTitle = css`
142
+ /* empty-title */
143
+ margin: 0 0 0.3rem;
144
+ font-size: 1.05rem;
145
+ font-weight: 650;
146
+ letter-spacing: -0.01em;
147
+ `;
148
+ const emptyDesc = css`
149
+ /* empty-desc */
150
+ margin: 0 auto;
151
+ color: var(--text-secondary);
152
+ font-size: 0.875rem;
153
+ max-width: 50ch;
154
+ `;
155
+ const emptyAction = css`
156
+ /* empty-action */
157
+ margin-top: 1rem;
158
+ `;
159
+ const statWrap = css`
160
+ /* stat */
161
+ text-align: center;
162
+ padding: 0.5rem;
163
+ `;
164
+ const statValueBase = css`
165
+ /* stat-value */
166
+ font-size: 1.35rem;
167
+ font-weight: 700;
168
+ line-height: 1.1;
169
+ font-variant-numeric: tabular-nums;
170
+ `;
171
+ const statValueTones = {
172
+ neutral: css`
173
+ /* stat-value-neutral */
174
+ ${statValueBase}
175
+ `,
176
+ success: css`
177
+ /* stat-value-success */
178
+ ${statValueBase}
179
+ color: var(--status-approved);
180
+ `,
181
+ warning: css`
182
+ /* stat-value-warning */
183
+ ${statValueBase}
184
+ color: var(--status-new);
185
+ `
186
+ };
187
+ const statLabel = css`
188
+ /* stat-label */
189
+ color: var(--text-secondary);
190
+ font-size: 0.72rem;
191
+ text-transform: uppercase;
192
+ letter-spacing: 0.06em;
193
+ `;
194
+ const metaText = css`
195
+ /* meta */
196
+ margin: 0;
197
+ color: var(--text-secondary);
198
+ font-size: 0.82rem;
199
+ `;
200
+ const metaMono = css`
201
+ /* meta-mono */
202
+ ${metaText}
203
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
204
+ `;
205
+ const metaCode = css`
206
+ /* meta-code */
207
+ ${metaMono}
208
+ font-size: 0.85em;
209
+ `;
210
+ const metaPre = css`
211
+ /* meta-pre */
212
+ ${metaMono}
213
+ white-space: pre-wrap;
214
+ word-break: break-word;
215
+ background: var(--surface-card);
216
+ border: 1px solid var(--border);
217
+ border-radius: var(--radius-sm);
218
+ padding: 0.75rem;
219
+ font-size: 0.8rem;
220
+ overflow: auto;
221
+ `;
222
+ const metaDanger = css`
223
+ /* meta-danger */
224
+ ${metaText}
225
+ color: var(--status-rejected);
226
+ `;
227
+ const metaCenter = css`
228
+ /* meta-center */
229
+ ${metaText}
230
+ text-align: center;
231
+ `;
232
+ /** Small status pill with a color tone. */
233
+ const Badge = ({ tone = "neutral", icon = false, children }) => {
234
+ return /* @__PURE__ */ jsxs("span", {
235
+ class: badgeTones[tone],
236
+ children: [icon ? /* @__PURE__ */ jsx(Icon, {
237
+ name: toneIcons[tone],
238
+ size: "sm"
239
+ }) : null, children]
240
+ });
241
+ };
242
+ /** Map a build or snapshot status string to its badge tone. */
243
+ function statusTone(status) {
244
+ if (isSuccessStatus(status)) return "success";
245
+ if (isDangerStatus(status)) return "danger";
246
+ if (isWarningStatus(status)) return "warning";
247
+ if (isInfoStatus(status)) return "info";
248
+ return "neutral";
249
+ }
250
+ function isSuccessStatus(status) {
251
+ return status === "approved" || status === "unchanged" || status === "completed";
252
+ }
253
+ function isDangerStatus(status) {
254
+ return status === "rejected" || status === "failed";
255
+ }
256
+ function isWarningStatus(status) {
257
+ return status === "changed" || status === "new" || status === "reviewing";
258
+ }
259
+ function isInfoStatus(status) {
260
+ return status === "capturing" || status === "comparing" || status === "pending" || status === "queued" || status === "running";
261
+ }
262
+ /** Alert banner with an optional title. */
263
+ const Alert = ({ tone = "info", title, children }) => {
264
+ return /* @__PURE__ */ jsxs("div", {
265
+ class: alertTones[tone],
266
+ role: "alert",
267
+ children: [/* @__PURE__ */ jsx("span", {
268
+ class: alertIconTones[tone],
269
+ children: /* @__PURE__ */ jsx(Icon, { name: toneIcons[tone] })
270
+ }), /* @__PURE__ */ jsxs("div", { children: [title ? /* @__PURE__ */ jsx("strong", {
271
+ class: alertTitle,
272
+ children: title
273
+ }) : null, /* @__PURE__ */ jsx("div", { children })] })]
274
+ });
275
+ };
276
+ const alertTitle = css`
277
+ /* alert-title */
278
+ display: block;
279
+ margin-bottom: 0.2rem;
280
+ font-weight: 650;
281
+ `;
282
+ /** Centered empty state with an optional title and call-to-action. */ const EmptyState = ({ title, description, action }) => {
283
+ return /* @__PURE__ */ jsxs("div", {
284
+ class: emptyWrap,
285
+ children: [
286
+ title ? /* @__PURE__ */ jsx("h2", {
287
+ class: emptyTitle,
288
+ children: title
289
+ }) : null,
290
+ description ? /* @__PURE__ */ jsx("p", {
291
+ class: emptyDesc,
292
+ children: description
293
+ }) : null,
294
+ action ? /* @__PURE__ */ jsx("div", {
295
+ class: emptyAction,
296
+ children: action
297
+ }) : null
298
+ ]
299
+ });
300
+ };
301
+ /** Centered statistic value with a label and an optional value tone. */
302
+ const Stat = ({ label, value, tone = "neutral" }) => {
303
+ return /* @__PURE__ */ jsxs("div", {
304
+ class: statWrap,
305
+ children: [/* @__PURE__ */ jsx("div", {
306
+ class: statValueTones[tone],
307
+ children: String(value)
308
+ }), /* @__PURE__ */ jsx("div", {
309
+ class: statLabel,
310
+ children: label
311
+ })]
312
+ });
313
+ };
314
+ /** Muted secondary text for metadata and descriptions (not a form hint). */
315
+ const Meta = ({ as = "p", mono = false, tone = "neutral", center = false, children }) => {
316
+ const cls = as === "pre" ? metaPre : as === "code" ? metaCode : tone === "danger" ? metaDanger : center ? metaCenter : mono ? metaMono : metaText;
317
+ if (as === "span") return /* @__PURE__ */ jsx("span", {
318
+ class: cls,
319
+ children
320
+ });
321
+ if (as === "div") return /* @__PURE__ */ jsx("div", {
322
+ class: cls,
323
+ children
324
+ });
325
+ if (as === "code") return /* @__PURE__ */ jsx("code", {
326
+ class: cls,
327
+ children
328
+ });
329
+ if (as === "pre") return /* @__PURE__ */ jsx("pre", {
330
+ class: cls,
331
+ children
332
+ });
333
+ return /* @__PURE__ */ jsx("p", {
334
+ class: cls,
335
+ children
336
+ });
337
+ };
338
+ //#endregion
339
+ export { Alert, Badge, EmptyState, Meta, Stat, statusTone };
340
+
341
+ //# sourceMappingURL=feedback.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback.mjs","names":[],"sources":["../../src/ui/feedback.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\nimport type { IconName } from \"./icons/paths.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype BadgeTone = \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\";\ntype StatTone = \"neutral\" | \"success\" | \"warning\";\n\nconst badgeBase = css`\n /* badge */\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.1rem 0.55rem;\n border-radius: var(--radius-pill);\n font-size: var(--text-xs);\n font-weight: 600;\n line-height: 1.5;\n border: 1px solid var(--border);\n background: var(--surface-muted);\n color: var(--text-secondary);\n white-space: nowrap;\n`;\n\nconst badgeTones: Record<BadgeTone, Promise<string>> = {\n neutral: css`\n /* badge-neutral */\n ${badgeBase}\n `,\n success: css`\n /* badge-success */\n ${badgeBase}\n background: var(--status-approved-bg);\n color: var(--status-approved-fg);\n border-color: var(--status-approved-border);\n `,\n warning: css`\n /* badge-warning */\n ${badgeBase}\n background: var(--status-new-bg);\n color: var(--status-new-fg);\n border-color: var(--status-new-border);\n `,\n danger: css`\n /* badge-danger */\n ${badgeBase}\n background: var(--status-rejected-bg);\n color: var(--status-rejected-fg);\n border-color: var(--status-rejected-border);\n `,\n info: css`\n /* badge-info */\n ${badgeBase}\n background: var(--status-info-bg);\n color: var(--accent-fg);\n border-color: var(--status-info-border);\n `,\n};\n\n/** Status glyph per tone so state never relies on color alone. */\nconst toneIcons: Record<BadgeTone, IconName> = {\n neutral: \"info\",\n success: \"check-circle\",\n warning: \"alert-triangle\",\n danger: \"x-circle\",\n info: \"info\",\n};\n\nconst alertBase = css`\n /* alert */\n display: flex;\n gap: 0.6rem;\n align-items: flex-start;\n border-radius: var(--radius);\n padding: 0.75rem 0.9rem;\n border: 1px solid var(--border);\n background: var(--surface-card);\n font-size: var(--text-base);\n`;\n\nconst alertTones: Record<BadgeTone, Promise<string>> = {\n neutral: css`\n /* alert-neutral */\n ${alertBase}\n `,\n info: css`\n /* alert-info */\n ${alertBase}\n border-color: var(--status-info-border);\n background: var(--status-info-bg);\n `,\n success: css`\n /* alert-success */\n ${alertBase}\n border-color: var(--status-approved-border);\n background: var(--status-approved-bg);\n `,\n warning: css`\n /* alert-warning */\n ${alertBase}\n border-color: var(--status-new-border);\n background: var(--status-new-bg);\n `,\n danger: css`\n /* alert-danger */\n ${alertBase}\n border-color: var(--status-rejected-border);\n background: var(--status-rejected-bg);\n `,\n};\n\nconst alertIconTones: Record<BadgeTone, Promise<string>> = {\n neutral: css`\n /* alert-icon-neutral */\n margin-top: 0.1rem;\n color: var(--text-secondary);\n `,\n info: css`\n /* alert-icon-info */\n margin-top: 0.1rem;\n color: var(--accent-fg);\n `,\n success: css`\n /* alert-icon-success */\n margin-top: 0.1rem;\n color: var(--status-approved);\n `,\n warning: css`\n /* alert-icon-warning */\n margin-top: 0.1rem;\n color: var(--status-new);\n `,\n danger: css`\n /* alert-icon-danger */\n margin-top: 0.1rem;\n color: var(--status-rejected);\n `,\n};\n\nconst emptyWrap = css`\n /* empty */\n text-align: center;\n padding: 2rem 1rem;\n border: 1px dashed var(--border);\n border-radius: var(--radius);\n background: var(--surface-card);\n`;\n\nconst emptyTitle = css`\n /* empty-title */\n margin: 0 0 0.3rem;\n font-size: 1.05rem;\n font-weight: 650;\n letter-spacing: -0.01em;\n`;\n\nconst emptyDesc = css`\n /* empty-desc */\n margin: 0 auto;\n color: var(--text-secondary);\n font-size: 0.875rem;\n max-width: 50ch;\n`;\n\nconst emptyAction = css`\n /* empty-action */\n margin-top: 1rem;\n`;\n\nconst statWrap = css`\n /* stat */\n text-align: center;\n padding: 0.5rem;\n`;\n\nconst statValueBase = css`\n /* stat-value */\n font-size: 1.35rem;\n font-weight: 700;\n line-height: 1.1;\n font-variant-numeric: tabular-nums;\n`;\n\nconst statValueTones: Record<StatTone, Promise<string>> = {\n neutral: css`\n /* stat-value-neutral */\n ${statValueBase}\n `,\n success: css`\n /* stat-value-success */\n ${statValueBase}\n color: var(--status-approved);\n `,\n warning: css`\n /* stat-value-warning */\n ${statValueBase}\n color: var(--status-new);\n `,\n};\n\nconst statLabel = css`\n /* stat-label */\n color: var(--text-secondary);\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n`;\n\nconst metaText = css`\n /* meta */\n margin: 0;\n color: var(--text-secondary);\n font-size: 0.82rem;\n`;\n\nconst metaMono = css`\n /* meta-mono */\n ${metaText}\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n`;\n\nconst metaCode = css`\n /* meta-code */\n ${metaMono}\n font-size: 0.85em;\n`;\n\nconst metaPre = css`\n /* meta-pre */\n ${metaMono}\n white-space: pre-wrap;\n word-break: break-word;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n padding: 0.75rem;\n font-size: 0.8rem;\n overflow: auto;\n`;\n\nconst metaDanger = css`\n /* meta-danger */\n ${metaText}\n color: var(--status-rejected);\n`;\n\nconst metaCenter = css`\n /* meta-center */\n ${metaText}\n text-align: center;\n`;\n\ntype MetaAs = \"p\" | \"span\" | \"div\" | \"code\" | \"pre\";\ntype MetaTone = \"neutral\" | \"danger\";\n\n/** Small status pill with a color tone. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Badge: FC<{ tone?: BadgeTone; icon?: boolean; children?: unknown }> = ({\n tone = \"neutral\",\n icon = false,\n children,\n}) => {\n return (\n <span class={badgeTones[tone]}>\n {icon ? <Icon name={toneIcons[tone]} size=\"sm\" /> : null}\n {children}\n </span>\n );\n};\n\n/** Map a build or snapshot status string to its badge tone. */\nexport function statusTone(status: string): BadgeTone {\n if (isSuccessStatus(status)) {\n return \"success\";\n }\n if (isDangerStatus(status)) {\n return \"danger\";\n }\n if (isWarningStatus(status)) {\n return \"warning\";\n }\n if (isInfoStatus(status)) {\n return \"info\";\n }\n return \"neutral\";\n}\n\nfunction isSuccessStatus(status: string): boolean {\n return status === \"approved\" || status === \"unchanged\" || status === \"completed\";\n}\n\nfunction isDangerStatus(status: string): boolean {\n return status === \"rejected\" || status === \"failed\";\n}\n\nfunction isWarningStatus(status: string): boolean {\n return status === \"changed\" || status === \"new\" || status === \"reviewing\";\n}\n\nfunction isInfoStatus(status: string): boolean {\n return (\n status === \"capturing\" ||\n status === \"comparing\" ||\n status === \"pending\" ||\n status === \"queued\" ||\n status === \"running\"\n );\n}\n\n/** Alert banner with an optional title. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Alert: FC<{ tone?: BadgeTone; title?: string; children?: unknown }> = ({\n tone = \"info\",\n title,\n children,\n}) => {\n return (\n <div class={alertTones[tone]} role=\"alert\">\n <span class={alertIconTones[tone]}>\n <Icon name={toneIcons[tone]} />\n </span>\n <div>\n {title ? <strong class={alertTitle}>{title}</strong> : null}\n <div>{children}</div>\n </div>\n </div>\n );\n};\n\nconst alertTitle = css`\n /* alert-title */\n display: block;\n margin-bottom: 0.2rem;\n font-weight: 650;\n`;\n\n/** Centered empty state with an optional title and call-to-action. */ // eslint-disable-next-line promise-function-async -- JSX component return type\nexport const EmptyState: FC<{ title?: string; description?: string; action?: unknown }> = ({\n title,\n description,\n action,\n}) => {\n return (\n <div class={emptyWrap}>\n {title ? <h2 class={emptyTitle}>{title}</h2> : null}\n {description ? <p class={emptyDesc}>{description}</p> : null}\n {action ? <div class={emptyAction}>{action}</div> : null}\n </div>\n );\n};\n\n/** Centered statistic value with a label and an optional value tone. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Stat: FC<{ label: string; value: string | number; tone?: StatTone }> = ({\n label,\n value,\n tone = \"neutral\",\n}) => {\n return (\n <div class={statWrap}>\n <div class={statValueTones[tone]}>{String(value)}</div>\n <div class={statLabel}>{label}</div>\n </div>\n );\n};\n\n/** Muted secondary text for metadata and descriptions (not a form hint). */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Meta: FC<{\n as?: MetaAs;\n mono?: boolean;\n tone?: MetaTone;\n center?: boolean;\n children?: unknown;\n}> = ({ as = \"p\", mono = false, tone = \"neutral\", center = false, children }) => {\n const cls =\n as === \"pre\"\n ? metaPre\n : as === \"code\"\n ? metaCode\n : tone === \"danger\"\n ? metaDanger\n : center\n ? metaCenter\n : mono\n ? metaMono\n : metaText;\n if (as === \"span\") {\n return <span class={cls}>{children}</span>;\n }\n if (as === \"div\") {\n return <div class={cls}>{children}</div>;\n }\n if (as === \"code\") {\n return <code class={cls}>{children}</code>;\n }\n if (as === \"pre\") {\n return <pre class={cls}>{children}</pre>;\n }\n return <p class={cls}>{children}</p>;\n};\n"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { css } from "./css.mjs";
5
+ import { Icon } from "./icons/icon.mjs";
6
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
7
+ //#region src/ui/filter-input.tsx
8
+ const filterWrap = css`
9
+ /* filter-input */
10
+ position: relative;
11
+ display: inline-flex;
12
+ align-items: center;
13
+ min-width: min(100%, 260px);
14
+ & svg {
15
+ position: absolute;
16
+ left: 0.65rem;
17
+ color: var(--text-muted);
18
+ pointer-events: none;
19
+ }
20
+ & input {
21
+ width: 100%;
22
+ min-height: 36px;
23
+ padding: 0.35rem 0.7rem 0.35rem 2rem;
24
+ background: var(--surface-card);
25
+ color: var(--text-primary);
26
+ border: 1px solid var(--border);
27
+ border-radius: var(--radius);
28
+ font: inherit;
29
+ font-size: var(--text-base);
30
+ }
31
+ & input::placeholder {
32
+ color: var(--text-muted);
33
+ }
34
+ `;
35
+ /**
36
+ * Client-side filter box. Matches its text against `data-filter-text` on
37
+ * elements marked `data-filter-item` inside the nearest `[data-filter-scope]`
38
+ * (or the whole page). Pair with a `data-filter-empty` element to show a
39
+ * "no matches" message.
40
+ */
41
+ const FilterInput = ({ label, placeholder = "Filter…" }) => {
42
+ return /* @__PURE__ */ jsxs("label", {
43
+ class: filterWrap,
44
+ children: [/* @__PURE__ */ jsx(Icon, {
45
+ name: "search",
46
+ size: "sm"
47
+ }), /* @__PURE__ */ jsx("input", {
48
+ type: "search",
49
+ "data-filter-input": true,
50
+ "aria-label": label,
51
+ placeholder
52
+ })]
53
+ });
54
+ };
55
+ //#endregion
56
+ export { FilterInput };
57
+
58
+ //# sourceMappingURL=filter-input.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-input.mjs","names":[],"sources":["../../src/ui/filter-input.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst filterWrap = css`\n /* filter-input */\n position: relative;\n display: inline-flex;\n align-items: center;\n min-width: min(100%, 260px);\n & svg {\n position: absolute;\n left: 0.65rem;\n color: var(--text-muted);\n pointer-events: none;\n }\n & input {\n width: 100%;\n min-height: 36px;\n padding: 0.35rem 0.7rem 0.35rem 2rem;\n background: var(--surface-card);\n color: var(--text-primary);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n font: inherit;\n font-size: var(--text-base);\n }\n & input::placeholder {\n color: var(--text-muted);\n }\n`;\n\n/**\n * Client-side filter box. Matches its text against `data-filter-text` on\n * elements marked `data-filter-item` inside the nearest `[data-filter-scope]`\n * (or the whole page). Pair with a `data-filter-empty` element to show a\n * \"no matches\" message.\n */\nexport const FilterInput: FC<{ label: string; placeholder?: string }> = ({\n label,\n placeholder = \"Filter…\",\n}) => {\n return (\n <label class={filterWrap}>\n <Icon name=\"search\" size=\"sm\" />\n <input type=\"search\" data-filter-input aria-label={label} placeholder={placeholder} />\n </label>\n );\n};\n"],"mappings":";;;;;;;AAMA,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCtB,MAAa,eAA4D,EACvE,OACA,cAAc,gBACV;CACJ,OACE,qBAAC,SAAD;EAAO,OAAO;EAAd,UAAA,CACE,oBAAC,MAAD;GAAM,MAAK;GAAS,MAAK;EAAM,CAAA,GAC/B,oBAAC,SAAD;GAAO,MAAK;GAAS,qBAAA;GAAkB,cAAY;GAAoB;EAAc,CAAA,CAChF;;AAEX"}