@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.
- package/dist/{admin-app-impl-sTQD2d-3.d.ts → admin-app-impl-CsH-gVGz.d.ts} +7 -7
- package/dist/{admin-app-impl-sTQD2d-3.d.ts.map → admin-app-impl-CsH-gVGz.d.ts.map} +1 -1
- package/dist/components/api-key-dialogs.d.ts +1 -1
- package/dist/components/elements/sidebar-layout.js +1 -1
- package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/dev-tool/dev-tool-core.js +2 -2
- package/dist/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/dev-tool/index.d.ts.map +1 -1
- package/dist/dev-tool/index.js +2 -8
- package/dist/dev-tool/index.js.map +1 -1
- package/dist/esm/components/api-key-dialogs.d.ts +1 -1
- package/dist/esm/components/elements/sidebar-layout.js +1 -1
- package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
- package/dist/esm/dev-tool/dev-tool-core.js +1 -1
- package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
- package/dist/esm/dev-tool/index.d.ts.map +1 -1
- package/dist/esm/dev-tool/index.js +2 -8
- package/dist/esm/dev-tool/index.js.map +1 -1
- package/dist/esm/generated/quetzal-translations.d.ts +2 -2
- package/dist/esm/in-page-ui/dev-environment.d.ts +5 -0
- package/dist/esm/in-page-ui/dev-environment.d.ts.map +1 -0
- package/dist/esm/in-page-ui/dev-environment.js +18 -0
- package/dist/esm/in-page-ui/dev-environment.js.map +1 -0
- package/dist/esm/in-page-ui/issue-card.d.ts +52 -0
- package/dist/esm/in-page-ui/issue-card.d.ts.map +1 -0
- package/dist/esm/in-page-ui/issue-card.js +392 -0
- package/dist/esm/in-page-ui/issue-card.js.map +1 -0
- package/dist/esm/in-page-ui/issue-card.test.d.ts +1 -0
- package/dist/esm/in-page-ui/issue-card.test.js +60 -0
- package/dist/esm/in-page-ui/issue-card.test.js.map +1 -0
- package/dist/esm/in-page-ui/logo.d.ts +5 -0
- package/dist/esm/in-page-ui/logo.d.ts.map +1 -0
- package/dist/esm/in-page-ui/logo.js +6 -0
- package/dist/esm/in-page-ui/logo.js.map +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +4 -4
- package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +140 -15
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +2 -2
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +95 -12
- package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/esm/lib/hexclave-app/apps/implementations/common.js +2 -2
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js +1 -0
- package/dist/esm/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -1
- package/dist/esm/pushed-config-error-overlay/index.js +42 -343
- package/dist/esm/pushed-config-error-overlay/index.js.map +1 -1
- package/dist/esm/setup-error-overlay/index.d.ts +16 -0
- package/dist/esm/setup-error-overlay/index.d.ts.map +1 -0
- package/dist/esm/setup-error-overlay/index.js +106 -0
- package/dist/esm/setup-error-overlay/index.js.map +1 -0
- package/dist/esm/setup-error-overlay/index.test.d.ts +1 -0
- package/dist/esm/setup-error-overlay/index.test.js +125 -0
- package/dist/esm/setup-error-overlay/index.test.js.map +1 -0
- package/dist/generated/quetzal-translations.d.ts +1 -1
- package/dist/{hexclave-provider-BKxi3Vy-.d.ts → hexclave-provider-BA97jMlx.d.ts} +2 -2
- package/dist/{hexclave-provider-BKxi3Vy-.d.ts.map → hexclave-provider-BA97jMlx.d.ts.map} +1 -1
- package/dist/in-page-ui/dev-environment.d.ts +5 -0
- package/dist/in-page-ui/dev-environment.d.ts.map +1 -0
- package/dist/in-page-ui/dev-environment.js +19 -0
- package/dist/in-page-ui/dev-environment.js.map +1 -0
- package/dist/in-page-ui/issue-card.d.ts +52 -0
- package/dist/in-page-ui/issue-card.d.ts.map +1 -0
- package/dist/in-page-ui/issue-card.js +397 -0
- package/dist/in-page-ui/issue-card.js.map +1 -0
- package/dist/in-page-ui/issue-card.test.d.ts +1 -0
- package/dist/in-page-ui/issue-card.test.js +59 -0
- package/dist/in-page-ui/issue-card.test.js.map +1 -0
- package/dist/in-page-ui/logo.d.ts +5 -0
- package/dist/in-page-ui/logo.d.ts.map +1 -0
- package/dist/in-page-ui/logo.js +7 -0
- package/dist/in-page-ui/logo.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js +140 -15
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +94 -11
- package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/common.js +2 -2
- package/dist/lib/hexclave-app/apps/implementations/index.d.ts +1 -1
- package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js +1 -0
- package/dist/lib/hexclave-app/apps/implementations/redirect-page-urls.test.js.map +1 -1
- package/dist/lib/hexclave-app/apps/implementations/server-app-impl.d.ts +1 -1
- package/dist/providers/hexclave-provider.d.ts +1 -1
- package/dist/providers/translation-provider.d.ts +1 -1
- package/dist/pushed-config-error-overlay/index.d.ts.map +1 -1
- package/dist/pushed-config-error-overlay/index.js +41 -342
- package/dist/pushed-config-error-overlay/index.js.map +1 -1
- package/dist/{quetzal-translations-XeH3zQ9l.d.ts → quetzal-translations-CK45_4Dm.d.ts} +3 -3
- package/dist/{quetzal-translations-XeH3zQ9l.d.ts.map → quetzal-translations-CK45_4Dm.d.ts.map} +1 -1
- package/dist/setup-error-overlay/index.d.ts +16 -0
- package/dist/setup-error-overlay/index.d.ts.map +1 -0
- package/dist/setup-error-overlay/index.js +108 -0
- package/dist/setup-error-overlay/index.js.map +1 -0
- package/dist/setup-error-overlay/index.test.d.ts +1 -0
- package/dist/setup-error-overlay/index.test.js +124 -0
- package/dist/setup-error-overlay/index.test.js.map +1 -0
- package/dist/{translation-provider-WOaZXqAz.d.ts → translation-provider-DQ3BJepJ.d.ts} +2 -2
- package/dist/{translation-provider-WOaZXqAz.d.ts.map → translation-provider-DQ3BJepJ.d.ts.map} +1 -1
- package/package.json +4 -4
- package/src/dev-tool/dev-tool-core.ts +1 -4
- package/src/dev-tool/index.ts +4 -15
- package/src/in-page-ui/dev-environment.ts +33 -0
- package/src/in-page-ui/issue-card.test.ts +67 -0
- package/src/in-page-ui/issue-card.ts +500 -0
- package/src/in-page-ui/logo.ts +8 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.cross-domain.test.ts +158 -0
- package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +125 -17
- package/src/lib/hexclave-app/apps/implementations/redirect-page-urls.test.ts +1 -0
- package/src/pushed-config-error-overlay/index.ts +45 -386
- package/src/setup-error-overlay/index.test.ts +133 -0
- package/src/setup-error-overlay/index.ts +150 -0
|
@@ -5,258 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
import { captureError } from "@hexclave/shared/dist/utils/errors";
|
|
7
7
|
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { canMountIntoDom, getGlobalUiInstance, h, setGlobalUiInstance, setHtml } from "../in-page-ui/dom";
|
|
8
|
+
import { isLikelyDevelopmentEnvironment } from "../in-page-ui/dev-environment";
|
|
9
|
+
import { canMountIntoDom, getGlobalUiInstance, h, setGlobalUiInstance } from "../in-page-ui/dom";
|
|
10
|
+
import { getIssueCardCSS, renderIssueCard, renderIssuePill, trapFocusInIssueCard, type IssueCardFocusTrap } from "../in-page-ui/issue-card";
|
|
12
11
|
import type { StackClientApp } from "../lib/hexclave-app";
|
|
13
12
|
|
|
14
13
|
const GLOBAL_INSTANCE_KEY = "__hexclave-pushed-config-error-overlay";
|
|
15
14
|
const MINIMIZED_STORAGE_KEY = "hexclave-pushed-config-error-minimized-key";
|
|
16
15
|
const REFRESH_INTERVAL_MS = 5_000;
|
|
17
|
-
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>';
|
|
18
|
-
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>';
|
|
19
16
|
|
|
20
17
|
type ConfigIssue = {
|
|
21
18
|
kind: "error" | "warning",
|
|
22
19
|
messages: string[],
|
|
23
20
|
};
|
|
24
21
|
|
|
25
|
-
const css =
|
|
26
|
-
.hexclave-config-error-overlay .hce-backdrop {
|
|
27
|
-
position: fixed;
|
|
28
|
-
inset: 0;
|
|
29
|
-
z-index: 2147483647;
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
padding: 24px;
|
|
34
|
-
background: rgba(0, 0, 0, 0.46);
|
|
35
|
-
backdrop-filter: blur(6px);
|
|
36
|
-
overflow: auto;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.hexclave-config-error-overlay .hce-card {
|
|
40
|
-
--hce-status: var(--sdt-error);
|
|
41
|
-
width: min(720px, calc(100vw - 32px));
|
|
42
|
-
max-height: min(640px, calc(100dvh - 48px));
|
|
43
|
-
border: 1px solid color-mix(in srgb, var(--hce-status) 35%, var(--sdt-border));
|
|
44
|
-
border-radius: 18px;
|
|
45
|
-
background: var(--sdt-overlay-bg);
|
|
46
|
-
box-shadow: var(--sdt-shadow);
|
|
47
|
-
backdrop-filter: blur(18px);
|
|
48
|
-
display: flex;
|
|
49
|
-
overflow: hidden;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.hexclave-config-error-overlay .hce-card-warning {
|
|
53
|
-
--hce-status: var(--sdt-warning);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.hexclave-config-error-overlay .hce-card-inner {
|
|
57
|
-
padding: 18px;
|
|
58
|
-
width: 100%;
|
|
59
|
-
overflow: auto;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.hexclave-config-error-overlay .hce-header {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: flex-start;
|
|
65
|
-
justify-content: space-between;
|
|
66
|
-
gap: 12px;
|
|
67
|
-
margin-bottom: 12px;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.hexclave-config-error-overlay .hce-title-row {
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: flex-start;
|
|
73
|
-
gap: 10px;
|
|
74
|
-
min-width: 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.hexclave-config-error-overlay .hce-logo {
|
|
78
|
-
flex-shrink: 0;
|
|
79
|
-
display: flex;
|
|
80
|
-
align-items: center;
|
|
81
|
-
justify-content: center;
|
|
82
|
-
width: 34px;
|
|
83
|
-
height: 34px;
|
|
84
|
-
border-radius: 10px;
|
|
85
|
-
background: var(--hce-status);
|
|
86
|
-
color: white;
|
|
87
|
-
box-shadow: 0 10px 30px color-mix(in srgb, var(--hce-status) 32%, transparent);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.hexclave-config-error-overlay .hce-badge {
|
|
91
|
-
display: inline-flex;
|
|
92
|
-
flex-shrink: 0;
|
|
93
|
-
padding: 2px 6px;
|
|
94
|
-
border-radius: 999px;
|
|
95
|
-
background: var(--hce-status);
|
|
96
|
-
color: white;
|
|
97
|
-
font-size: 10px;
|
|
98
|
-
font-weight: 700;
|
|
99
|
-
letter-spacing: 0.06em;
|
|
100
|
-
text-transform: uppercase;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.hexclave-config-error-overlay .hce-title {
|
|
104
|
-
color: var(--sdt-text);
|
|
105
|
-
margin-top: 4px;
|
|
106
|
-
font-size: 18px;
|
|
107
|
-
font-weight: 700;
|
|
108
|
-
line-height: 1.25;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.hexclave-config-error-overlay .hce-actions {
|
|
112
|
-
display: flex;
|
|
113
|
-
gap: 4px;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.hexclave-config-error-overlay .hce-icon-btn {
|
|
117
|
-
display: inline-flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
justify-content: center;
|
|
120
|
-
width: 28px;
|
|
121
|
-
height: 28px;
|
|
122
|
-
border: 1px solid var(--sdt-border);
|
|
123
|
-
border-radius: 8px;
|
|
124
|
-
background: var(--sdt-bg-elevated);
|
|
125
|
-
color: var(--sdt-text-secondary);
|
|
126
|
-
cursor: pointer;
|
|
127
|
-
font: inherit;
|
|
128
|
-
line-height: 1;
|
|
129
|
-
vertical-align: top;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.hexclave-config-error-overlay .hce-icon-btn svg {
|
|
133
|
-
display: block;
|
|
134
|
-
flex-shrink: 0;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.hexclave-config-error-overlay .hce-text-btn {
|
|
138
|
-
align-items: center;
|
|
139
|
-
gap: 6px;
|
|
140
|
-
min-height: 28px;
|
|
141
|
-
padding: 0 10px;
|
|
142
|
-
width: auto;
|
|
143
|
-
font-size: 12px;
|
|
144
|
-
line-height: 1;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.hexclave-config-error-overlay .hce-icon-btn:hover {
|
|
148
|
-
background: var(--sdt-bg-hover);
|
|
149
|
-
color: var(--sdt-text);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.hexclave-config-error-overlay .hce-body {
|
|
153
|
-
color: var(--sdt-text-secondary);
|
|
154
|
-
font-size: 14px;
|
|
155
|
-
line-height: 1.5;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.hexclave-config-error-overlay .hce-message-header {
|
|
159
|
-
display: flex;
|
|
160
|
-
align-items: center;
|
|
161
|
-
justify-content: space-between;
|
|
162
|
-
gap: 10px;
|
|
163
|
-
margin-top: 14px;
|
|
164
|
-
margin-bottom: 8px;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.hexclave-config-error-overlay .hce-message-label {
|
|
168
|
-
color: var(--sdt-text);
|
|
169
|
-
font-size: 12px;
|
|
170
|
-
font-weight: 650;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.hexclave-config-error-overlay .hce-message {
|
|
174
|
-
padding: 12px;
|
|
175
|
-
max-height: min(260px, max(96px, 30dvh));
|
|
176
|
-
overflow: auto;
|
|
177
|
-
border: 1px solid var(--sdt-border-subtle);
|
|
178
|
-
border-radius: 10px;
|
|
179
|
-
background: var(--sdt-bg-subtle);
|
|
180
|
-
color: var(--sdt-text);
|
|
181
|
-
font-family: var(--sdt-font-mono);
|
|
182
|
-
font-size: 12px;
|
|
183
|
-
white-space: pre-wrap;
|
|
184
|
-
overflow-wrap: anywhere;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
.hexclave-config-error-overlay .hce-footer {
|
|
188
|
-
margin-top: 10px;
|
|
189
|
-
color: var(--sdt-text-tertiary);
|
|
190
|
-
font-size: 12px;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.hexclave-config-error-overlay .hce-pill {
|
|
194
|
-
position: fixed;
|
|
195
|
-
right: 18px;
|
|
196
|
-
bottom: 18px;
|
|
197
|
-
z-index: 2147483647;
|
|
198
|
-
display: flex;
|
|
199
|
-
align-items: center;
|
|
200
|
-
gap: 8px;
|
|
201
|
-
padding: 8px 12px 8px 8px;
|
|
202
|
-
--hce-status: var(--sdt-error);
|
|
203
|
-
border: 1px solid color-mix(in srgb, var(--hce-status) 35%, var(--sdt-border));
|
|
204
|
-
border-radius: 999px;
|
|
205
|
-
background: var(--sdt-overlay-bg);
|
|
206
|
-
box-shadow: var(--sdt-trigger-shadow);
|
|
207
|
-
color: var(--sdt-text);
|
|
208
|
-
cursor: pointer;
|
|
209
|
-
font: inherit;
|
|
210
|
-
backdrop-filter: blur(18px);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.hexclave-config-error-overlay .hce-pill-warning {
|
|
214
|
-
--hce-status: var(--sdt-warning);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.hexclave-config-error-overlay .hce-pill-logo {
|
|
218
|
-
display: flex;
|
|
219
|
-
align-items: center;
|
|
220
|
-
justify-content: center;
|
|
221
|
-
width: 26px;
|
|
222
|
-
height: 26px;
|
|
223
|
-
border-radius: 999px;
|
|
224
|
-
background: var(--hce-status);
|
|
225
|
-
color: white;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
@media (max-height: 520px) {
|
|
229
|
-
.hexclave-config-error-overlay .hce-backdrop {
|
|
230
|
-
align-items: flex-start;
|
|
231
|
-
padding: 12px;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.hexclave-config-error-overlay .hce-card {
|
|
235
|
-
width: min(720px, calc(100vw - 24px));
|
|
236
|
-
max-height: calc(100dvh - 24px);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.hexclave-config-error-overlay .hce-card-inner {
|
|
240
|
-
padding: 12px;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.hexclave-config-error-overlay .hce-header {
|
|
244
|
-
margin-bottom: 8px;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
.hexclave-config-error-overlay .hce-title {
|
|
248
|
-
font-size: 16px;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.hexclave-config-error-overlay .hce-body {
|
|
252
|
-
font-size: 13px;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
.hexclave-config-error-overlay .hce-message {
|
|
256
|
-
max-height: max(80px, 24dvh);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
`;
|
|
22
|
+
const css = getIssueCardCSS(".hexclave-config-error-overlay");
|
|
260
23
|
|
|
261
24
|
function storageGet(key: string): string | null {
|
|
262
25
|
try {
|
|
@@ -283,53 +46,7 @@ function storageRemove(key: string): void {
|
|
|
283
46
|
}
|
|
284
47
|
|
|
285
48
|
function shouldMount(): boolean {
|
|
286
|
-
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const nodeEnv = envVars.NODE_ENV;
|
|
291
|
-
if (nodeEnv !== undefined) {
|
|
292
|
-
return nodeEnv === "development";
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
const url = new URL(window.location.href);
|
|
297
|
-
if (url.protocol === "file:") {
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
} catch {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
return isLocalhost(window.location.href);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async function copyTextToClipboard(text: string): Promise<void> {
|
|
307
|
-
const clipboard: unknown = Reflect.get(navigator, "clipboard");
|
|
308
|
-
const writeText = clipboard != null && typeof clipboard === "object"
|
|
309
|
-
? Reflect.get(clipboard, "writeText")
|
|
310
|
-
: null;
|
|
311
|
-
if (typeof writeText === "function") {
|
|
312
|
-
await writeText.call(clipboard, text);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const textarea = h("textarea", {
|
|
317
|
-
style: {
|
|
318
|
-
position: "fixed",
|
|
319
|
-
left: "-9999px",
|
|
320
|
-
top: "0",
|
|
321
|
-
opacity: "0",
|
|
322
|
-
},
|
|
323
|
-
readonly: "true",
|
|
324
|
-
}) as HTMLTextAreaElement;
|
|
325
|
-
textarea.value = text;
|
|
326
|
-
document.body.appendChild(textarea);
|
|
327
|
-
textarea.select();
|
|
328
|
-
const copied = document.execCommand("copy");
|
|
329
|
-
textarea.remove();
|
|
330
|
-
if (!copied) {
|
|
331
|
-
throw new Error("Browser refused to copy the config error message.");
|
|
332
|
-
}
|
|
49
|
+
return canMountIntoDom() && isLikelyDevelopmentEnvironment();
|
|
333
50
|
}
|
|
334
51
|
|
|
335
52
|
function buildConfigIssueAiPrompt(issue: ConfigIssue): string {
|
|
@@ -363,12 +80,23 @@ export function mountPushedConfigErrorOverlay(app: StackClientApp<true>): () =>
|
|
|
363
80
|
let lastErrorKey: string | null = null;
|
|
364
81
|
let lastConsoleErrorKey: string | null = null;
|
|
365
82
|
let minimized = false;
|
|
83
|
+
// The card element is replaced on every poll, so the trap has to be rebound to the new one each time or it would keep
|
|
84
|
+
// trapping Tab inside the detached card. Focus is only *moved* into the card when a card first appears for an issue;
|
|
85
|
+
// doing it on every render would yank focus out of whatever the developer is doing every few seconds.
|
|
86
|
+
let focusedIssueKey: string | null = null;
|
|
87
|
+
let focusTrap: IssueCardFocusTrap | null = null;
|
|
88
|
+
const releaseFocusIfHeld = () => {
|
|
89
|
+
focusTrap?.release();
|
|
90
|
+
focusTrap = null;
|
|
91
|
+
focusedIssueKey = null;
|
|
92
|
+
};
|
|
366
93
|
|
|
367
94
|
const render = (issue: ConfigIssue | null) => {
|
|
368
95
|
root.replaceChildren(style);
|
|
369
96
|
if (issue == null) {
|
|
370
97
|
lastErrorKey = null;
|
|
371
98
|
minimized = false;
|
|
99
|
+
releaseFocusIfHeld();
|
|
372
100
|
return;
|
|
373
101
|
}
|
|
374
102
|
|
|
@@ -400,113 +128,43 @@ export function mountPushedConfigErrorOverlay(app: StackClientApp<true>): () =>
|
|
|
400
128
|
}
|
|
401
129
|
|
|
402
130
|
if (minimized) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
131
|
+
releaseFocusIfHeld();
|
|
132
|
+
root.appendChild(renderIssuePill({
|
|
133
|
+
kind: issue.kind,
|
|
134
|
+
label: issue.kind === "error" ? "Config error" : "Config warning",
|
|
135
|
+
ariaLabel: `Show Hexclave config ${issueLabel}`,
|
|
408
136
|
onClick: () => {
|
|
409
137
|
minimized = false;
|
|
410
138
|
storageRemove(MINIMIZED_STORAGE_KEY);
|
|
411
139
|
render(issue);
|
|
412
140
|
},
|
|
413
|
-
}
|
|
414
|
-
logoSpan,
|
|
415
|
-
h("span", null, issue.kind === "error" ? "Config error" : "Config warning")));
|
|
141
|
+
}));
|
|
416
142
|
return;
|
|
417
143
|
}
|
|
418
144
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
onError: (copyError) => {
|
|
436
|
-
captureError("pushed-config-error-overlay-copy", copyError);
|
|
437
|
-
copyButton.textContent = "Copy failed";
|
|
438
|
-
setTimeout(() => {
|
|
439
|
-
setHtml(copyButton, `${COPY_ICON_SVG}Copy`);
|
|
440
|
-
}, 1500);
|
|
441
|
-
},
|
|
442
|
-
});
|
|
145
|
+
root.appendChild(renderIssueCard({
|
|
146
|
+
kind: issue.kind,
|
|
147
|
+
badge: `Config ${issueLabel}`,
|
|
148
|
+
title: issueTitle,
|
|
149
|
+
bodyText,
|
|
150
|
+
messageLabel: issue.kind === "error" ? "Error message" : "Warning message",
|
|
151
|
+
message: issueMessage,
|
|
152
|
+
footerText,
|
|
153
|
+
ariaLabel: `Hexclave config ${issueLabel}`,
|
|
154
|
+
aiPrompt: buildConfigIssueAiPrompt(issue),
|
|
155
|
+
onCopyError: (copyError) => captureError("pushed-config-error-overlay-copy", copyError),
|
|
156
|
+
onCopyAiPromptError: (copyError) => captureError("pushed-config-error-overlay-copy-ai-prompt", copyError),
|
|
157
|
+
onMinimize: () => {
|
|
158
|
+
minimized = true;
|
|
159
|
+
storageSet(MINIMIZED_STORAGE_KEY, issueKey);
|
|
160
|
+
render(issue);
|
|
443
161
|
},
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
title: "Copy AI prompt",
|
|
451
|
-
"aria-label": issue.kind === "error" ? "Copy AI prompt for config error" : "Copy AI prompt for config warning",
|
|
452
|
-
onClick: () => {
|
|
453
|
-
runAsynchronously(async () => {
|
|
454
|
-
await copyTextToClipboard(buildConfigIssueAiPrompt(issue));
|
|
455
|
-
aiPromptCopyButton.textContent = "✓";
|
|
456
|
-
setTimeout(() => {
|
|
457
|
-
setHtml(aiPromptCopyButton, COPY_ICON_SVG);
|
|
458
|
-
}, 1500);
|
|
459
|
-
}, {
|
|
460
|
-
noErrorLogging: true,
|
|
461
|
-
onError: (copyError) => {
|
|
462
|
-
captureError("pushed-config-error-overlay-copy-ai-prompt", copyError);
|
|
463
|
-
aiPromptCopyButton.textContent = "!";
|
|
464
|
-
setTimeout(() => {
|
|
465
|
-
setHtml(aiPromptCopyButton, COPY_ICON_SVG);
|
|
466
|
-
}, 1500);
|
|
467
|
-
},
|
|
468
|
-
});
|
|
469
|
-
},
|
|
470
|
-
});
|
|
471
|
-
setHtml(aiPromptCopyButton, COPY_ICON_SVG);
|
|
472
|
-
|
|
473
|
-
root.appendChild(h("div", { className: "hce-backdrop" },
|
|
474
|
-
h("div", { className: issue.kind === "error" ? "hce-card" : "hce-card hce-card-warning", role: "alertdialog", "aria-modal": "true", "aria-label": `Hexclave config ${issueLabel}` },
|
|
475
|
-
h("div", { className: "hce-card-inner" },
|
|
476
|
-
h("div", { className: "hce-header" },
|
|
477
|
-
h("div", { className: "hce-title-row" },
|
|
478
|
-
logoSpan,
|
|
479
|
-
h("div", null,
|
|
480
|
-
h("span", { className: "hce-badge" }, `Config ${issueLabel}`),
|
|
481
|
-
h("div", { className: "hce-title" }, issueTitle),
|
|
482
|
-
),
|
|
483
|
-
),
|
|
484
|
-
h("div", { className: "hce-actions" },
|
|
485
|
-
aiPromptCopyButton,
|
|
486
|
-
h("button", {
|
|
487
|
-
className: "hce-icon-btn",
|
|
488
|
-
type: "button",
|
|
489
|
-
title: "Minimize",
|
|
490
|
-
"aria-label": issue.kind === "error" ? "Minimize config error" : "Minimize config warning",
|
|
491
|
-
onClick: () => {
|
|
492
|
-
minimized = true;
|
|
493
|
-
storageSet(MINIMIZED_STORAGE_KEY, issueKey);
|
|
494
|
-
render(issue);
|
|
495
|
-
},
|
|
496
|
-
}, "–"),
|
|
497
|
-
),
|
|
498
|
-
),
|
|
499
|
-
h("div", { className: "hce-body" },
|
|
500
|
-
bodyText,
|
|
501
|
-
h("div", { className: "hce-message-header" },
|
|
502
|
-
h("div", { className: "hce-message-label" }, issue.kind === "error" ? "Error message" : "Warning message"),
|
|
503
|
-
copyButton,
|
|
504
|
-
),
|
|
505
|
-
h("div", { className: "hce-message" }, issueMessage),
|
|
506
|
-
h("div", { className: "hce-footer" }, footerText),
|
|
507
|
-
),
|
|
508
|
-
),
|
|
509
|
-
)));
|
|
162
|
+
}));
|
|
163
|
+
const isNewIssue = focusedIssueKey !== issueKey;
|
|
164
|
+
const previouslyFocused = isNewIssue ? undefined : focusTrap?.previouslyFocused ?? null;
|
|
165
|
+
focusTrap?.release({ restoreFocus: isNewIssue });
|
|
166
|
+
focusedIssueKey = issueKey;
|
|
167
|
+
focusTrap = trapFocusInIssueCard(root, { moveFocusIntoCard: isNewIssue, previouslyFocused });
|
|
510
168
|
};
|
|
511
169
|
|
|
512
170
|
const refresh = () => {
|
|
@@ -540,6 +198,7 @@ export function mountPushedConfigErrorOverlay(app: StackClientApp<true>): () =>
|
|
|
540
198
|
const cleanup = () => {
|
|
541
199
|
disposed = true;
|
|
542
200
|
clearInterval(interval);
|
|
201
|
+
releaseFocusIfHeld();
|
|
543
202
|
root.remove();
|
|
544
203
|
if (getGlobalUiInstance(GLOBAL_INSTANCE_KEY)?.cleanup === cleanup) {
|
|
545
204
|
setGlobalUiInstance(GLOBAL_INSTANCE_KEY, null);
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
// @vitest-environment jsdom
|
|
6
|
+
|
|
7
|
+
import { HexclaveSetupError } from "@hexclave/shared/dist/utils/errors";
|
|
8
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { SETUP_ERROR_OVERLAY_GLOBAL_INSTANCE_KEY, showSetupErrorOverlay } from ".";
|
|
10
|
+
|
|
11
|
+
function createSetupError() {
|
|
12
|
+
return new HexclaveSetupError({
|
|
13
|
+
title: "A domain in your authentication flow is not one of your project's trusted domains",
|
|
14
|
+
message: "Nested cross-domain auth callback URL https://demo.example.test/ is not trusted.",
|
|
15
|
+
howToFix: ["Add https://demo.example.test to your project's trusted domains."],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function overlayRoots() {
|
|
20
|
+
return document.querySelectorAll(".hexclave-setup-error-overlay");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("setup error overlay", () => {
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.unstubAllEnvs();
|
|
26
|
+
for (const root of overlayRoots()) {
|
|
27
|
+
root.remove();
|
|
28
|
+
}
|
|
29
|
+
Reflect.deleteProperty(window, SETUP_ERROR_OVERLAY_GLOBAL_INSTANCE_KEY);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders setup errors with their fix instructions, even outside development", () => {
|
|
33
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
34
|
+
const error = createSetupError();
|
|
35
|
+
|
|
36
|
+
const cleanup = showSetupErrorOverlay(error);
|
|
37
|
+
try {
|
|
38
|
+
expect(overlayRoots()).toHaveLength(1);
|
|
39
|
+
const text = document.body.textContent;
|
|
40
|
+
expect(text).toContain(error.title);
|
|
41
|
+
expect(text).toContain(error.message);
|
|
42
|
+
expect(text).toContain(error.howToFix[0]);
|
|
43
|
+
expect(document.querySelector("[role='alertdialog']")).not.toBeNull();
|
|
44
|
+
} finally {
|
|
45
|
+
cleanup();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
expect(overlayRoots()).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("minimizes into a pill and back", () => {
|
|
52
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
53
|
+
const cleanup = showSetupErrorOverlay(createSetupError());
|
|
54
|
+
try {
|
|
55
|
+
const minimizeButton = document.querySelector<HTMLButtonElement>("[aria-label='Minimize Hexclave setup error']");
|
|
56
|
+
expect(minimizeButton).not.toBeNull();
|
|
57
|
+
minimizeButton?.click();
|
|
58
|
+
expect(document.querySelector("[role='alertdialog']")).toBeNull();
|
|
59
|
+
|
|
60
|
+
const pill = document.querySelector<HTMLButtonElement>("[aria-label='Show Hexclave setup error']");
|
|
61
|
+
expect(pill).not.toBeNull();
|
|
62
|
+
pill?.click();
|
|
63
|
+
expect(document.querySelector("[role='alertdialog']")).not.toBeNull();
|
|
64
|
+
} finally {
|
|
65
|
+
cleanup();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("keeps keyboard focus inside the card and gives it back afterwards", () => {
|
|
70
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
71
|
+
const pageButton = document.body.appendChild(document.createElement("button"));
|
|
72
|
+
pageButton.focus();
|
|
73
|
+
|
|
74
|
+
const cleanup = showSetupErrorOverlay(createSetupError());
|
|
75
|
+
try {
|
|
76
|
+
const card = document.querySelector<HTMLElement>("[role='alertdialog']");
|
|
77
|
+
expect(document.activeElement).toBe(card);
|
|
78
|
+
|
|
79
|
+
const cardButtons = [...(card?.querySelectorAll("button") ?? [])];
|
|
80
|
+
expect(cardButtons.length).toBeGreaterThan(1);
|
|
81
|
+
cardButtons[cardButtons.length - 1].focus();
|
|
82
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true }));
|
|
83
|
+
expect(document.activeElement).toBe(cardButtons[0]);
|
|
84
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true, shiftKey: true }));
|
|
85
|
+
expect(document.activeElement).toBe(cardButtons[cardButtons.length - 1]);
|
|
86
|
+
|
|
87
|
+
// Tabbing away from the card is what the trap exists to prevent, even when the page itself moves focus.
|
|
88
|
+
pageButton.focus();
|
|
89
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true, cancelable: true }));
|
|
90
|
+
expect(document.activeElement).toBe(cardButtons[0]);
|
|
91
|
+
} finally {
|
|
92
|
+
cleanup();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
expect(document.activeElement).toBe(pageButton);
|
|
96
|
+
pageButton.remove();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("keeps the first card instead of stacking later errors on top of it", () => {
|
|
100
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
101
|
+
const firstError = createSetupError();
|
|
102
|
+
const cleanup = showSetupErrorOverlay(firstError);
|
|
103
|
+
try {
|
|
104
|
+
const secondCleanup = showSetupErrorOverlay(new HexclaveSetupError({
|
|
105
|
+
title: "Another setup error",
|
|
106
|
+
message: "Another setup error happened.",
|
|
107
|
+
howToFix: ["Fix the other thing."],
|
|
108
|
+
}));
|
|
109
|
+
secondCleanup();
|
|
110
|
+
|
|
111
|
+
expect(overlayRoots()).toHaveLength(1);
|
|
112
|
+
expect(document.body.textContent).toContain(firstError.message);
|
|
113
|
+
expect(document.body.textContent).not.toContain("Another setup error happened.");
|
|
114
|
+
} finally {
|
|
115
|
+
cleanup();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("only shows errors that are not setup errors while developing", () => {
|
|
120
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
121
|
+
showSetupErrorOverlay(new Error("Some internal failure."))();
|
|
122
|
+
expect(overlayRoots()).toHaveLength(0);
|
|
123
|
+
|
|
124
|
+
vi.stubEnv("NODE_ENV", "development");
|
|
125
|
+
const cleanup = showSetupErrorOverlay(new Error("Some internal failure."));
|
|
126
|
+
try {
|
|
127
|
+
expect(overlayRoots()).toHaveLength(1);
|
|
128
|
+
expect(document.body.textContent).toContain("Some internal failure.");
|
|
129
|
+
} finally {
|
|
130
|
+
cleanup();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|