@hexclave/next 1.0.75 → 1.0.76

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 (114) hide show
  1. package/dist/{admin-app-impl-sTQD2d-3.d.ts → admin-app-impl-CsH-gVGz.d.ts} +7 -7
  2. package/dist/{admin-app-impl-sTQD2d-3.d.ts.map → admin-app-impl-CsH-gVGz.d.ts.map} +1 -1
  3. package/dist/components/api-key-dialogs.d.ts +1 -1
  4. package/dist/components/elements/sidebar-layout.js +1 -1
  5. package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
  6. package/dist/dev-tool/dev-tool-core.js +2 -2
  7. package/dist/dev-tool/dev-tool-core.js.map +1 -1
  8. package/dist/dev-tool/index.d.ts.map +1 -1
  9. package/dist/dev-tool/index.js +2 -8
  10. package/dist/dev-tool/index.js.map +1 -1
  11. package/dist/esm/components/api-key-dialogs.d.ts +1 -1
  12. package/dist/esm/components/elements/sidebar-layout.js +1 -1
  13. package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
  14. package/dist/esm/dev-tool/dev-tool-core.js +1 -1
  15. package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
  16. package/dist/esm/dev-tool/index.d.ts.map +1 -1
  17. package/dist/esm/dev-tool/index.js +2 -8
  18. package/dist/esm/dev-tool/index.js.map +1 -1
  19. package/dist/esm/generated/quetzal-translations.d.ts +2 -2
  20. package/dist/esm/in-page-ui/dev-environment.d.ts +5 -0
  21. package/dist/esm/in-page-ui/dev-environment.d.ts.map +1 -0
  22. package/dist/esm/in-page-ui/dev-environment.js +18 -0
  23. package/dist/esm/in-page-ui/dev-environment.js.map +1 -0
  24. package/dist/esm/in-page-ui/issue-card.d.ts +52 -0
  25. package/dist/esm/in-page-ui/issue-card.d.ts.map +1 -0
  26. package/dist/esm/in-page-ui/issue-card.js +392 -0
  27. package/dist/esm/in-page-ui/issue-card.js.map +1 -0
  28. package/dist/esm/in-page-ui/issue-card.test.d.ts +1 -0
  29. package/dist/esm/in-page-ui/issue-card.test.js +60 -0
  30. package/dist/esm/in-page-ui/issue-card.test.js.map +1 -0
  31. package/dist/esm/in-page-ui/logo.d.ts +5 -0
  32. package/dist/esm/in-page-ui/logo.d.ts.map +1 -0
  33. package/dist/esm/in-page-ui/logo.js +6 -0
  34. package/dist/esm/in-page-ui/logo.js.map +1 -0
  35. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +4 -4
  36. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  37. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +140 -15
  38. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  39. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +2 -2
  40. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  41. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +95 -12
  42. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  43. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +2 -2
  44. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js +1 -0
  45. package/dist/esm/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js.map +1 -1
  46. package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -1
  47. package/dist/esm/pushed-config-error-overlay/index.js +42 -343
  48. package/dist/esm/pushed-config-error-overlay/index.js.map +1 -1
  49. package/dist/esm/setup-error-overlay/index.d.ts +16 -0
  50. package/dist/esm/setup-error-overlay/index.d.ts.map +1 -0
  51. package/dist/esm/setup-error-overlay/index.js +106 -0
  52. package/dist/esm/setup-error-overlay/index.js.map +1 -0
  53. package/dist/esm/setup-error-overlay/index.test.d.ts +1 -0
  54. package/dist/esm/setup-error-overlay/index.test.js +125 -0
  55. package/dist/esm/setup-error-overlay/index.test.js.map +1 -0
  56. package/dist/generated/quetzal-translations.d.ts +1 -1
  57. package/dist/{hexclave-provider-BKxi3Vy-.d.ts → hexclave-provider-BA97jMlx.d.ts} +2 -2
  58. package/dist/{hexclave-provider-BKxi3Vy-.d.ts.map → hexclave-provider-BA97jMlx.d.ts.map} +1 -1
  59. package/dist/in-page-ui/dev-environment.d.ts +5 -0
  60. package/dist/in-page-ui/dev-environment.d.ts.map +1 -0
  61. package/dist/in-page-ui/dev-environment.js +19 -0
  62. package/dist/in-page-ui/dev-environment.js.map +1 -0
  63. package/dist/in-page-ui/issue-card.d.ts +52 -0
  64. package/dist/in-page-ui/issue-card.d.ts.map +1 -0
  65. package/dist/in-page-ui/issue-card.js +397 -0
  66. package/dist/in-page-ui/issue-card.js.map +1 -0
  67. package/dist/in-page-ui/issue-card.test.d.ts +1 -0
  68. package/dist/in-page-ui/issue-card.test.js +59 -0
  69. package/dist/in-page-ui/issue-card.test.js.map +1 -0
  70. package/dist/in-page-ui/logo.d.ts +5 -0
  71. package/dist/in-page-ui/logo.d.ts.map +1 -0
  72. package/dist/in-page-ui/logo.js +7 -0
  73. package/dist/in-page-ui/logo.js.map +1 -0
  74. package/dist/index.d.ts +1 -1
  75. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
  76. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +140 -15
  77. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
  78. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
  79. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +94 -11
  80. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  81. package/dist/lib/hexclave-app/apps/implementations/common.js +2 -2
  82. package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
  83. package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js +1 -0
  84. package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js.map +1 -1
  85. package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
  86. package/dist/providers/hexclave-provider.d.ts +1 -1
  87. package/dist/providers/translation-provider.d.ts +1 -1
  88. package/dist/pushed-config-error-overlay/index.d.ts.map +1 -1
  89. package/dist/pushed-config-error-overlay/index.js +41 -342
  90. package/dist/pushed-config-error-overlay/index.js.map +1 -1
  91. package/dist/{quetzal-translations-XeH3zQ9l.d.ts → quetzal-translations-CK45_4Dm.d.ts} +3 -3
  92. package/dist/{quetzal-translations-XeH3zQ9l.d.ts.map → quetzal-translations-CK45_4Dm.d.ts.map} +1 -1
  93. package/dist/setup-error-overlay/index.d.ts +16 -0
  94. package/dist/setup-error-overlay/index.d.ts.map +1 -0
  95. package/dist/setup-error-overlay/index.js +108 -0
  96. package/dist/setup-error-overlay/index.js.map +1 -0
  97. package/dist/setup-error-overlay/index.test.d.ts +1 -0
  98. package/dist/setup-error-overlay/index.test.js +124 -0
  99. package/dist/setup-error-overlay/index.test.js.map +1 -0
  100. package/dist/{translation-provider-WOaZXqAz.d.ts → translation-provider-DQ3BJepJ.d.ts} +2 -2
  101. package/dist/{translation-provider-WOaZXqAz.d.ts.map → translation-provider-DQ3BJepJ.d.ts.map} +1 -1
  102. package/package.json +4 -4
  103. package/src/dev-tool/dev-tool-core.ts +1 -4
  104. package/src/dev-tool/index.ts +4 -15
  105. package/src/in-page-ui/dev-environment.ts +33 -0
  106. package/src/in-page-ui/issue-card.test.ts +67 -0
  107. package/src/in-page-ui/issue-card.ts +500 -0
  108. package/src/in-page-ui/logo.ts +8 -0
  109. package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +158 -0
  110. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +125 -17
  111. package/src/lib/hexclave-app/apps/implementations/redirect-page-urls.test.ts +1 -0
  112. package/src/pushed-config-error-overlay/index.ts +45 -386
  113. package/src/setup-error-overlay/index.test.ts +133 -0
  114. package/src/setup-error-overlay/index.ts +150 -0
@@ -0,0 +1,500 @@
1
+
2
+ //===========================================
3
+ // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
4
+ //===========================================
5
+ // Shared renderer for Hexclave's in-page issue cards: the modal-style card (plus its minimized pill) that the SDK uses
6
+ // to tell developers about problems it cannot recover from, like a broken pushed config or an untrusted auth domain.
7
+ // Every issue surface renders the same shape, so the markup, styles and clipboard handling live here and each feature
8
+ // only supplies its copy and callbacks.
9
+
10
+ import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
11
+ import { getInPageUiBaseCSS } from "./base-styles";
12
+ import { h, setHtml } from "./dom";
13
+ import { HEXCLAVE_LOGO_SVG } from "./logo";
14
+
15
+ export type IssueCardKind = "error" | "warning";
16
+
17
+ const COPY_ICON_SVG = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
18
+
19
+ export function getIssueCardCSS(scopeSelector: string): string {
20
+ return getInPageUiBaseCSS(scopeSelector) + `
21
+ ${scopeSelector} .hic-backdrop {
22
+ position: fixed;
23
+ inset: 0;
24
+ z-index: 2147483647;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ padding: 24px;
29
+ background: rgba(0, 0, 0, 0.46);
30
+ backdrop-filter: blur(6px);
31
+ overflow: auto;
32
+ }
33
+
34
+ ${scopeSelector} .hic-card {
35
+ --hic-status: var(--sdt-error);
36
+ width: min(720px, calc(100vw - 32px));
37
+ max-height: min(640px, calc(100dvh - 48px));
38
+ border: 1px solid color-mix(in srgb, var(--hic-status) 35%, var(--sdt-border));
39
+ border-radius: 18px;
40
+ background: var(--sdt-overlay-bg);
41
+ box-shadow: var(--sdt-shadow);
42
+ backdrop-filter: blur(18px);
43
+ display: flex;
44
+ overflow: hidden;
45
+ }
46
+
47
+ ${scopeSelector} .hic-card-warning {
48
+ --hic-status: var(--sdt-warning);
49
+ }
50
+
51
+ ${scopeSelector} .hic-card-inner {
52
+ padding: 18px;
53
+ width: 100%;
54
+ overflow: auto;
55
+ }
56
+
57
+ ${scopeSelector} .hic-header {
58
+ display: flex;
59
+ align-items: flex-start;
60
+ justify-content: space-between;
61
+ gap: 12px;
62
+ margin-bottom: 12px;
63
+ }
64
+
65
+ ${scopeSelector} .hic-title-row {
66
+ display: flex;
67
+ align-items: flex-start;
68
+ gap: 10px;
69
+ min-width: 0;
70
+ }
71
+
72
+ ${scopeSelector} .hic-logo {
73
+ flex-shrink: 0;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ width: 34px;
78
+ height: 34px;
79
+ border-radius: 10px;
80
+ background: var(--hic-status);
81
+ color: white;
82
+ box-shadow: 0 10px 30px color-mix(in srgb, var(--hic-status) 32%, transparent);
83
+ }
84
+
85
+ ${scopeSelector} .hic-badge {
86
+ display: inline-flex;
87
+ flex-shrink: 0;
88
+ padding: 2px 6px;
89
+ border-radius: 999px;
90
+ background: var(--hic-status);
91
+ color: white;
92
+ font-size: 10px;
93
+ font-weight: 700;
94
+ letter-spacing: 0.06em;
95
+ text-transform: uppercase;
96
+ }
97
+
98
+ ${scopeSelector} .hic-title {
99
+ color: var(--sdt-text);
100
+ margin-top: 4px;
101
+ font-size: 18px;
102
+ font-weight: 700;
103
+ line-height: 1.25;
104
+ }
105
+
106
+ ${scopeSelector} .hic-actions {
107
+ display: flex;
108
+ gap: 4px;
109
+ }
110
+
111
+ ${scopeSelector} .hic-icon-btn {
112
+ display: inline-flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ width: 28px;
116
+ height: 28px;
117
+ border: 1px solid var(--sdt-border);
118
+ border-radius: 8px;
119
+ background: var(--sdt-bg-elevated);
120
+ color: var(--sdt-text-secondary);
121
+ cursor: pointer;
122
+ font: inherit;
123
+ line-height: 1;
124
+ vertical-align: top;
125
+ }
126
+
127
+ ${scopeSelector} .hic-icon-btn svg {
128
+ display: block;
129
+ flex-shrink: 0;
130
+ }
131
+
132
+ ${scopeSelector} .hic-text-btn {
133
+ align-items: center;
134
+ gap: 6px;
135
+ min-height: 28px;
136
+ padding: 0 10px;
137
+ width: auto;
138
+ font-size: 12px;
139
+ line-height: 1;
140
+ }
141
+
142
+ ${scopeSelector} .hic-icon-btn:hover {
143
+ background: var(--sdt-bg-hover);
144
+ color: var(--sdt-text);
145
+ }
146
+
147
+ ${scopeSelector} .hic-body {
148
+ color: var(--sdt-text-secondary);
149
+ font-size: 14px;
150
+ line-height: 1.5;
151
+ }
152
+
153
+ ${scopeSelector} .hic-message-header {
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: space-between;
157
+ gap: 10px;
158
+ margin-top: 14px;
159
+ margin-bottom: 8px;
160
+ }
161
+
162
+ ${scopeSelector} .hic-message-label {
163
+ color: var(--sdt-text);
164
+ font-size: 12px;
165
+ font-weight: 650;
166
+ }
167
+
168
+ ${scopeSelector} .hic-message {
169
+ padding: 12px;
170
+ max-height: min(260px, max(96px, 30dvh));
171
+ overflow: auto;
172
+ border: 1px solid var(--sdt-border-subtle);
173
+ border-radius: 10px;
174
+ background: var(--sdt-bg-subtle);
175
+ color: var(--sdt-text);
176
+ font-family: var(--sdt-font-mono);
177
+ font-size: 12px;
178
+ white-space: pre-wrap;
179
+ overflow-wrap: anywhere;
180
+ }
181
+
182
+ ${scopeSelector} .hic-fix-list {
183
+ margin: 8px 0 0;
184
+ padding-left: 18px;
185
+ color: var(--sdt-text);
186
+ font-size: 13px;
187
+ }
188
+
189
+ ${scopeSelector} .hic-fix-list li + li {
190
+ margin-top: 4px;
191
+ }
192
+
193
+ ${scopeSelector} .hic-footer {
194
+ margin-top: 10px;
195
+ color: var(--sdt-text-tertiary);
196
+ font-size: 12px;
197
+ }
198
+
199
+ ${scopeSelector} .hic-pill {
200
+ position: fixed;
201
+ right: 18px;
202
+ bottom: 18px;
203
+ z-index: 2147483647;
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 8px;
207
+ padding: 8px 12px 8px 8px;
208
+ --hic-status: var(--sdt-error);
209
+ border: 1px solid color-mix(in srgb, var(--hic-status) 35%, var(--sdt-border));
210
+ border-radius: 999px;
211
+ background: var(--sdt-overlay-bg);
212
+ box-shadow: var(--sdt-trigger-shadow);
213
+ color: var(--sdt-text);
214
+ cursor: pointer;
215
+ font: inherit;
216
+ backdrop-filter: blur(18px);
217
+ }
218
+
219
+ ${scopeSelector} .hic-pill-warning {
220
+ --hic-status: var(--sdt-warning);
221
+ }
222
+
223
+ ${scopeSelector} .hic-pill-logo {
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ width: 26px;
228
+ height: 26px;
229
+ border-radius: 999px;
230
+ background: var(--hic-status);
231
+ color: white;
232
+ }
233
+
234
+ @media (max-height: 520px) {
235
+ ${scopeSelector} .hic-backdrop {
236
+ align-items: flex-start;
237
+ padding: 12px;
238
+ }
239
+
240
+ ${scopeSelector} .hic-card {
241
+ width: min(720px, calc(100vw - 24px));
242
+ max-height: calc(100dvh - 24px);
243
+ }
244
+
245
+ ${scopeSelector} .hic-card-inner {
246
+ padding: 12px;
247
+ }
248
+
249
+ ${scopeSelector} .hic-header {
250
+ margin-bottom: 8px;
251
+ }
252
+
253
+ ${scopeSelector} .hic-title {
254
+ font-size: 16px;
255
+ }
256
+
257
+ ${scopeSelector} .hic-body {
258
+ font-size: 13px;
259
+ }
260
+
261
+ ${scopeSelector} .hic-message {
262
+ max-height: max(80px, 24dvh);
263
+ }
264
+ }
265
+ `;
266
+ }
267
+
268
+ export async function copyTextToClipboard(text: string): Promise<void> {
269
+ const clipboard: unknown = Reflect.get(navigator, "clipboard");
270
+ const writeText = clipboard != null && typeof clipboard === "object"
271
+ ? Reflect.get(clipboard, "writeText")
272
+ : null;
273
+ if (typeof writeText === "function") {
274
+ await writeText.call(clipboard, text);
275
+ return;
276
+ }
277
+
278
+ const textarea = h("textarea", {
279
+ style: {
280
+ position: "fixed",
281
+ left: "-9999px",
282
+ top: "0",
283
+ opacity: "0",
284
+ },
285
+ readonly: "true",
286
+ });
287
+ textarea.value = text;
288
+ document.body.appendChild(textarea);
289
+ textarea.select();
290
+ const copied = document.execCommand("copy");
291
+ textarea.remove();
292
+ if (!copied) {
293
+ throw new Error("Browser refused to copy the message.");
294
+ }
295
+ }
296
+
297
+ /**
298
+ * A button that copies `getText()` and briefly reports the outcome in its own label. Copying can fail for reasons
299
+ * outside our control (permissions, insecure contexts), so the failure is reported inline instead of being thrown into
300
+ * a click handler where nothing would show it.
301
+ */
302
+ function renderCopyButton(options: {
303
+ variant: "icon" | "text",
304
+ title: string,
305
+ ariaLabel: string,
306
+ getText: () => string,
307
+ onCopyError: (error: unknown) => void,
308
+ }): HTMLElement {
309
+ const idleHtml = options.variant === "text" ? `${COPY_ICON_SVG}Copy` : COPY_ICON_SVG;
310
+ const button = h("button", {
311
+ className: options.variant === "text" ? "hic-icon-btn hic-text-btn" : "hic-icon-btn",
312
+ type: "button",
313
+ title: options.title,
314
+ "aria-label": options.ariaLabel,
315
+ onClick: () => {
316
+ runAsynchronously(async () => {
317
+ await copyTextToClipboard(options.getText());
318
+ button.textContent = options.variant === "text" ? "Copied" : "✓";
319
+ setTimeout(() => setHtml(button, idleHtml), 1500);
320
+ }, {
321
+ noErrorLogging: true,
322
+ onError: (error) => {
323
+ options.onCopyError(error);
324
+ button.textContent = options.variant === "text" ? "Copy failed" : "!";
325
+ setTimeout(() => setHtml(button, idleHtml), 1500);
326
+ },
327
+ });
328
+ },
329
+ });
330
+ setHtml(button, idleHtml);
331
+ return button;
332
+ }
333
+
334
+ export type IssueCardOptions = {
335
+ kind: IssueCardKind,
336
+ /** Short uppercase label in the card's badge, eg. "Config error". */
337
+ badge: string,
338
+ title: string,
339
+ /** One or two sentences on what this means for the running app. */
340
+ bodyText: string,
341
+ messageLabel: string,
342
+ message: string,
343
+ /** Concrete steps the developer should take; rendered as a list above the footer. */
344
+ howToFix?: readonly string[],
345
+ footerText: string,
346
+ ariaLabel: string,
347
+ /** Prompt for the "copy AI prompt" button, ie. the message plus enough context for a coding agent to fix it. */
348
+ aiPrompt: string,
349
+ onCopyError: (error: unknown) => void,
350
+ onCopyAiPromptError: (error: unknown) => void,
351
+ onMinimize: () => void,
352
+ };
353
+
354
+ export function renderIssueCard(options: IssueCardOptions): HTMLElement {
355
+ const logoSpan = h("span", { className: "hic-logo" });
356
+ setHtml(logoSpan, HEXCLAVE_LOGO_SVG);
357
+
358
+ return h("div", { className: "hic-backdrop" },
359
+ h("div", { className: options.kind === "error" ? "hic-card" : "hic-card hic-card-warning", role: "alertdialog", "aria-modal": "true", "aria-label": options.ariaLabel, tabindex: "-1" },
360
+ h("div", { className: "hic-card-inner" },
361
+ h("div", { className: "hic-header" },
362
+ h("div", { className: "hic-title-row" },
363
+ logoSpan,
364
+ h("div", null,
365
+ h("span", { className: "hic-badge" }, options.badge),
366
+ h("div", { className: "hic-title" }, options.title),
367
+ ),
368
+ ),
369
+ h("div", { className: "hic-actions" },
370
+ renderCopyButton({
371
+ variant: "icon",
372
+ title: "Copy AI prompt",
373
+ ariaLabel: `Copy AI prompt for ${options.ariaLabel}`,
374
+ getText: () => options.aiPrompt,
375
+ onCopyError: options.onCopyAiPromptError,
376
+ }),
377
+ h("button", {
378
+ className: "hic-icon-btn",
379
+ type: "button",
380
+ title: "Minimize",
381
+ "aria-label": `Minimize ${options.ariaLabel}`,
382
+ onClick: options.onMinimize,
383
+ }, "–"),
384
+ ),
385
+ ),
386
+ h("div", { className: "hic-body" },
387
+ options.bodyText,
388
+ h("div", { className: "hic-message-header" },
389
+ h("div", { className: "hic-message-label" }, options.messageLabel),
390
+ renderCopyButton({
391
+ variant: "text",
392
+ title: `Copy ${options.messageLabel.toLowerCase()}`,
393
+ ariaLabel: `Copy ${options.ariaLabel} message`,
394
+ getText: () => options.message,
395
+ onCopyError: options.onCopyError,
396
+ }),
397
+ ),
398
+ h("div", { className: "hic-message" }, options.message),
399
+ options.howToFix == null || options.howToFix.length === 0 ? null : h("div", null,
400
+ h("div", { className: "hic-message-header" },
401
+ h("div", { className: "hic-message-label" }, "How to fix"),
402
+ ),
403
+ h("ul",
404
+ { className: "hic-fix-list" },
405
+ ...options.howToFix.map((step) => h("li", null, step)),
406
+ ),
407
+ ),
408
+ h("div", { className: "hic-footer" }, options.footerText),
409
+ ),
410
+ ),
411
+ ),
412
+ );
413
+ }
414
+
415
+ const FOCUSABLE_SELECTOR = "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])";
416
+
417
+ export type IssueCardFocusTrap = {
418
+ /**
419
+ * Whatever had focus before the card first appeared. Callers that rebind the trap to a re-rendered card pass it back
420
+ * in, so focus still returns to where the developer left it once the card is gone for good.
421
+ */
422
+ previouslyFocused: HTMLElement | null,
423
+ release: (releaseOptions?: { restoreFocus?: boolean }) => void,
424
+ };
425
+
426
+ /**
427
+ * Keeps keyboard focus inside a mounted card, which claims to be an `aria-modal` alertdialog and therefore has to behave
428
+ * like one: the flow behind the card is dead until the problem is fixed, so tabbing into it would only let a keyboard
429
+ * user wander through a page that cannot work while the card stays invisible to them.
430
+ *
431
+ * Call right after mounting the card, and release the trap when the card is minimized, replaced or removed. Releasing
432
+ * hands focus back to `previouslyFocused` unless `restoreFocus: false` is passed, which is what a caller that is about
433
+ * to rebind the trap to a freshly rendered card wants.
434
+ */
435
+ export function trapFocusInIssueCard(cardRoot: HTMLElement, options: {
436
+ moveFocusIntoCard?: boolean,
437
+ previouslyFocused?: HTMLElement | null,
438
+ } = {}): IssueCardFocusTrap {
439
+ const previouslyFocused = options.previouslyFocused !== undefined
440
+ ? options.previouslyFocused
441
+ : document.activeElement instanceof HTMLElement ? document.activeElement : null;
442
+ const card = cardRoot.querySelector(".hic-card");
443
+ if (!(card instanceof HTMLElement)) return { previouslyFocused, release: () => {} };
444
+
445
+ const onKeyDown = (event: KeyboardEvent) => {
446
+ if (event.key !== "Tab") return;
447
+ const focusable = [...card.querySelectorAll(FOCUSABLE_SELECTOR)].filter((element): element is HTMLElement => element instanceof HTMLElement);
448
+ const first = focusable[0] ?? card;
449
+ const last = focusable[focusable.length - 1] ?? card;
450
+ const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
451
+
452
+ let target: HTMLElement | null = null;
453
+ if (active == null || !card.contains(active)) {
454
+ // Focus is outside the card (the page moved it, or it never got in): pull it back to the card's near end.
455
+ target = event.shiftKey ? last : first;
456
+ } else if (active === last && !event.shiftKey) {
457
+ target = first;
458
+ } else if (active === first && event.shiftKey) {
459
+ target = last;
460
+ }
461
+ if (target == null) return;
462
+ event.preventDefault();
463
+ target.focus();
464
+ };
465
+
466
+ document.addEventListener("keydown", onKeyDown, true);
467
+ if (options.moveFocusIntoCard !== false) {
468
+ card.focus();
469
+ }
470
+
471
+ return {
472
+ previouslyFocused,
473
+ release: (releaseOptions) => {
474
+ document.removeEventListener("keydown", onKeyDown, true);
475
+ if (releaseOptions?.restoreFocus === false) return;
476
+ if (previouslyFocused != null && previouslyFocused.isConnected) {
477
+ previouslyFocused.focus();
478
+ }
479
+ },
480
+ };
481
+ }
482
+
483
+ export function renderIssuePill(options: {
484
+ kind: IssueCardKind,
485
+ label: string,
486
+ ariaLabel: string,
487
+ onClick: () => void,
488
+ }): HTMLElement {
489
+ const logoSpan = h("span", { className: "hic-pill-logo" });
490
+ setHtml(logoSpan, HEXCLAVE_LOGO_SVG);
491
+ return h("button", {
492
+ className: options.kind === "error" ? "hic-pill" : "hic-pill hic-pill-warning",
493
+ type: "button",
494
+ "aria-label": options.ariaLabel,
495
+ onClick: options.onClick,
496
+ },
497
+ logoSpan,
498
+ h("span", null, options.label),
499
+ );
500
+ }
@@ -0,0 +1,8 @@
1
+
2
+ //===========================================
3
+ // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
4
+ //===========================================
5
+ // Hexclave mark — hexagon outline with three radial bars, monochrome via currentColor so it inherits the surrounding
6
+ // text color. Sourced from apps/dashboard/public/hexclave-icon.svg (gradient + glow stripped; this is a tiny glyph, not
7
+ // the full brand mark). Shared by every in-page UI so the mark cannot drift between them.
8
+ export const HEXCLAVE_LOGO_SVG = '<svg width="16" height="16" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="miter"><path d="M 24 4 L 41.32 14 L 41.32 34 L 24 44 L 6.68 34 L 6.68 14 Z"/><path d="M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z" fill="currentColor" stroke="none"/><path d="M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z" fill="currentColor" stroke="none" transform="rotate(120 24 24)"/><path d="M 11 16.87 L 14 15.13 L 14 32.87 L 11 31.13 Z" fill="currentColor" stroke="none" transform="rotate(240 24 24)"/></svg>';