@stati/core 1.26.2 → 1.27.0
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/README.md +58 -40
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +11 -5
- package/dist/core/utils/callable-partials.utils.d.ts.map +1 -1
- package/dist/core/utils/callable-partials.utils.js +14 -5
- package/dist/core/utils/context-validation.utils.d.ts +45 -0
- package/dist/core/utils/context-validation.utils.d.ts.map +1 -0
- package/dist/core/utils/context-validation.utils.js +175 -0
- package/dist/core/utils/error-overlay.utils.d.ts.map +1 -1
- package/dist/core/utils/error-overlay.utils.js +747 -312
- package/dist/core/utils/index.d.ts +2 -0
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +4 -0
- package/dist/core/utils/partial-validation.utils.d.ts.map +1 -1
- package/dist/core/utils/partial-validation.utils.js +30 -81
- package/dist/core/utils/similarity.utils.d.ts +34 -0
- package/dist/core/utils/similarity.utils.d.ts.map +1 -0
- package/dist/core/utils/similarity.utils.js +73 -0
- package/dist/core/utils/template-errors.utils.d.ts +3 -1
- package/dist/core/utils/template-errors.utils.d.ts.map +1 -1
- package/dist/core/utils/template-errors.utils.js +68 -8
- package/package.json +1 -1
|
@@ -2,433 +2,845 @@
|
|
|
2
2
|
* Error overlay utility for displaying pretty errors in development mode.
|
|
3
3
|
* Provides a user-friendly interface for template rendering and build errors.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
5
|
+
const ERROR_HEADLINES = {
|
|
6
|
+
template: 'Template rendering failed',
|
|
7
|
+
build: 'Build process failed',
|
|
8
|
+
markdown: 'Markdown processing failed',
|
|
9
|
+
config: 'Configuration error',
|
|
10
|
+
};
|
|
11
|
+
const BOLT_PATH = 'M13 10V3L4 14h7v7l9-11h-7z';
|
|
12
|
+
/** Inline favicon, so the overlay never requests the site's own favicon. */
|
|
13
|
+
const FAVICON_HREF = `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${BOLT_PATH}"/></svg>`)}`;
|
|
14
|
+
const OVERLAY_STYLES = `
|
|
15
|
+
:root {
|
|
16
|
+
--canvas: 255 255 255;
|
|
17
|
+
--raised: 248 250 252;
|
|
18
|
+
--sunken: 246 248 250;
|
|
19
|
+
--ink: 11 13 18;
|
|
20
|
+
--muted: 71 85 105;
|
|
21
|
+
--faint: 91 101 119;
|
|
22
|
+
--line: 226 232 240;
|
|
23
|
+
--line-strong: 203 213 225;
|
|
24
|
+
--accent: 37 99 235;
|
|
25
|
+
--accent-ink: 29 78 216;
|
|
26
|
+
--on-accent: 255 255 255;
|
|
27
|
+
--danger: 185 28 28;
|
|
28
|
+
--danger-wash: 254 242 242;
|
|
29
|
+
--font-display: 'Archivo', 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
30
|
+
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
31
|
+
--font-mono: 'Fira Code', ui-monospace, 'Cascadia Code', Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
|
|
32
|
+
color-scheme: light;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@media (prefers-color-scheme: dark) {
|
|
36
|
+
:root {
|
|
37
|
+
--canvas: 2 6 23;
|
|
38
|
+
--raised: 15 23 42;
|
|
39
|
+
--sunken: 11 18 32;
|
|
40
|
+
--ink: 241 245 249;
|
|
41
|
+
--muted: 148 163 184;
|
|
42
|
+
--faint: 131 145 167;
|
|
43
|
+
--line: 30 41 59;
|
|
44
|
+
--line-strong: 51 65 85;
|
|
45
|
+
--accent: 96 165 250;
|
|
46
|
+
--accent-ink: 147 197 253;
|
|
47
|
+
--on-accent: 2 6 23;
|
|
48
|
+
--danger: 248 113 113;
|
|
49
|
+
--danger-wash: 38 12 20;
|
|
50
|
+
color-scheme: dark;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
*,
|
|
55
|
+
*::before,
|
|
56
|
+
*::after {
|
|
24
57
|
box-sizing: border-box;
|
|
25
58
|
}
|
|
26
59
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
color: #ffffff;
|
|
31
|
-
line-height: 1.6;
|
|
32
|
-
min-height: 100vh;
|
|
33
|
-
overflow-y: auto;
|
|
60
|
+
html {
|
|
61
|
+
background-color: rgb(var(--canvas));
|
|
62
|
+
scrollbar-color: rgb(var(--line-strong)) transparent;
|
|
34
63
|
}
|
|
35
64
|
|
|
36
|
-
|
|
65
|
+
body {
|
|
66
|
+
margin: 0;
|
|
37
67
|
min-height: 100vh;
|
|
38
68
|
display: flex;
|
|
39
69
|
flex-direction: column;
|
|
70
|
+
background-color: rgb(var(--canvas));
|
|
71
|
+
color: rgb(var(--ink));
|
|
72
|
+
font-family: var(--font-sans);
|
|
73
|
+
font-size: 16px;
|
|
74
|
+
line-height: 1.6;
|
|
75
|
+
font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1, 'cv11' 1;
|
|
76
|
+
-webkit-font-smoothing: antialiased;
|
|
77
|
+
-moz-osx-font-smoothing: grayscale;
|
|
40
78
|
}
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
background:
|
|
44
|
-
padding: 24px 32px;
|
|
45
|
-
box-shadow: 0 4px 20px rgba(255, 107, 107, 0.3);
|
|
80
|
+
::selection {
|
|
81
|
+
background-color: rgb(var(--accent) / 0.25);
|
|
46
82
|
}
|
|
47
83
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
84
|
+
:focus-visible {
|
|
85
|
+
outline: 2px solid rgb(var(--accent));
|
|
86
|
+
outline-offset: 2px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
h1,
|
|
90
|
+
h2,
|
|
91
|
+
p,
|
|
92
|
+
pre,
|
|
93
|
+
dl,
|
|
94
|
+
dd,
|
|
95
|
+
ul,
|
|
96
|
+
figure {
|
|
97
|
+
margin: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.sr-only {
|
|
101
|
+
position: absolute;
|
|
102
|
+
width: 1px;
|
|
103
|
+
height: 1px;
|
|
104
|
+
padding: 0;
|
|
105
|
+
margin: -1px;
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
clip: rect(0, 0, 0, 0);
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
border: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.shell {
|
|
113
|
+
width: 100%;
|
|
114
|
+
max-width: 72rem;
|
|
115
|
+
margin: 0 auto;
|
|
116
|
+
padding-left: 1rem;
|
|
117
|
+
padding-right: 1rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Header bar */
|
|
121
|
+
|
|
122
|
+
.bar {
|
|
123
|
+
border-bottom: 1px solid rgb(var(--line));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.bar .shell {
|
|
127
|
+
min-height: 3.5rem;
|
|
52
128
|
display: flex;
|
|
129
|
+
flex-wrap: wrap;
|
|
53
130
|
align-items: center;
|
|
54
|
-
|
|
131
|
+
justify-content: space-between;
|
|
132
|
+
gap: 0.75rem 1.5rem;
|
|
133
|
+
padding-top: 0.625rem;
|
|
134
|
+
padding-bottom: 0.625rem;
|
|
55
135
|
}
|
|
56
136
|
|
|
57
|
-
.
|
|
58
|
-
width: 32px;
|
|
59
|
-
height: 32px;
|
|
60
|
-
background: rgba(255, 255, 255, 0.2);
|
|
61
|
-
border-radius: 50%;
|
|
137
|
+
.brand {
|
|
62
138
|
display: flex;
|
|
63
139
|
align-items: center;
|
|
64
|
-
|
|
65
|
-
|
|
140
|
+
gap: 0.625rem;
|
|
141
|
+
min-width: 0;
|
|
66
142
|
}
|
|
67
143
|
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
144
|
+
.brand-mark {
|
|
145
|
+
width: 1.25rem;
|
|
146
|
+
height: 1.25rem;
|
|
147
|
+
flex: none;
|
|
148
|
+
color: rgb(var(--accent));
|
|
72
149
|
}
|
|
73
150
|
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
151
|
+
.brand-name {
|
|
152
|
+
font-family: var(--font-display);
|
|
153
|
+
font-size: 1.125rem;
|
|
154
|
+
font-weight: 800;
|
|
155
|
+
font-stretch: 112%;
|
|
156
|
+
letter-spacing: -0.02em;
|
|
157
|
+
line-height: 1;
|
|
80
158
|
}
|
|
81
159
|
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
margin-bottom: 24px;
|
|
87
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
160
|
+
.brand-rule {
|
|
161
|
+
width: 1px;
|
|
162
|
+
height: 1rem;
|
|
163
|
+
background-color: rgb(var(--line-strong));
|
|
88
164
|
}
|
|
89
165
|
|
|
90
|
-
.
|
|
91
|
-
font-size:
|
|
92
|
-
font-weight:
|
|
93
|
-
|
|
94
|
-
|
|
166
|
+
.brand-context {
|
|
167
|
+
font-size: 0.875rem;
|
|
168
|
+
font-weight: 500;
|
|
169
|
+
color: rgb(var(--muted));
|
|
170
|
+
white-space: nowrap;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.bar-actions {
|
|
95
174
|
display: flex;
|
|
96
175
|
align-items: center;
|
|
97
|
-
gap:
|
|
176
|
+
gap: 1rem;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.status {
|
|
180
|
+
display: inline-flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
gap: 0.5rem;
|
|
183
|
+
font-size: 0.8125rem;
|
|
184
|
+
color: rgb(var(--muted));
|
|
98
185
|
}
|
|
99
186
|
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
187
|
+
.status-dot {
|
|
188
|
+
width: 0.5rem;
|
|
189
|
+
height: 0.5rem;
|
|
190
|
+
flex: none;
|
|
191
|
+
border-radius: 50%;
|
|
192
|
+
border: 1px solid rgb(var(--faint));
|
|
193
|
+
transition: background-color 150ms, border-color 150ms;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.status[data-state='live'] .status-dot {
|
|
197
|
+
background-color: rgb(var(--ink));
|
|
198
|
+
border-color: rgb(var(--ink));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.status[data-state='off'] .status-dot {
|
|
202
|
+
border-color: rgb(var(--danger));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.button {
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: 0.625rem;
|
|
209
|
+
min-height: 2.25rem;
|
|
210
|
+
padding: 0.5rem 0.875rem;
|
|
211
|
+
border: 0;
|
|
103
212
|
border-radius: 8px;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
213
|
+
background-color: rgb(var(--accent));
|
|
214
|
+
color: rgb(var(--on-accent));
|
|
215
|
+
font: inherit;
|
|
216
|
+
font-size: 0.875rem;
|
|
217
|
+
font-weight: 600;
|
|
218
|
+
line-height: 1.25;
|
|
219
|
+
cursor: pointer;
|
|
220
|
+
transition: background-color 150ms;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.button:hover {
|
|
224
|
+
background-color: rgb(var(--accent-ink));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.button kbd {
|
|
228
|
+
padding: 0.0625rem 0.3125rem;
|
|
229
|
+
border: 1px solid rgb(var(--on-accent) / 0.4);
|
|
230
|
+
border-radius: 4px;
|
|
231
|
+
font-family: var(--font-mono);
|
|
232
|
+
font-feature-settings: normal;
|
|
233
|
+
font-variant-ligatures: none;
|
|
234
|
+
font-size: 0.75rem;
|
|
235
|
+
font-weight: 400;
|
|
236
|
+
line-height: 1.4;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Main content */
|
|
240
|
+
|
|
241
|
+
.main {
|
|
242
|
+
flex: 1;
|
|
243
|
+
padding-top: 2.5rem;
|
|
244
|
+
padding-bottom: 4rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.summary {
|
|
248
|
+
max-width: 56rem;
|
|
108
249
|
}
|
|
109
250
|
|
|
110
|
-
.
|
|
251
|
+
.headline {
|
|
252
|
+
font-family: var(--font-display);
|
|
253
|
+
font-size: 1.875rem;
|
|
254
|
+
font-weight: 800;
|
|
255
|
+
font-stretch: 108%;
|
|
256
|
+
letter-spacing: -0.015em;
|
|
257
|
+
line-height: 1.1;
|
|
258
|
+
text-wrap: balance;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.meta {
|
|
111
262
|
display: flex;
|
|
112
|
-
gap: 16px;
|
|
113
|
-
margin-bottom: 16px;
|
|
114
263
|
flex-wrap: wrap;
|
|
264
|
+
align-items: center;
|
|
265
|
+
gap: 0.5rem 0.75rem;
|
|
266
|
+
margin-top: 0.875rem;
|
|
267
|
+
font-size: 0.875rem;
|
|
268
|
+
color: rgb(var(--muted));
|
|
115
269
|
}
|
|
116
270
|
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
padding: 8px 12px;
|
|
120
|
-
border-radius: 6px;
|
|
121
|
-
font-size: 14px;
|
|
122
|
-
display: flex;
|
|
271
|
+
.type-badge {
|
|
272
|
+
display: inline-flex;
|
|
123
273
|
align-items: center;
|
|
124
|
-
|
|
274
|
+
padding: 0.125rem 0.5rem;
|
|
275
|
+
border: 1px solid rgb(var(--line-strong));
|
|
276
|
+
border-radius: 4px;
|
|
277
|
+
font-size: 0.75rem;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
line-height: 1.5;
|
|
280
|
+
color: rgb(var(--ink));
|
|
281
|
+
text-transform: capitalize;
|
|
125
282
|
}
|
|
126
283
|
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
font-
|
|
284
|
+
.meta-time {
|
|
285
|
+
font-family: var(--font-mono);
|
|
286
|
+
font-feature-settings: normal;
|
|
287
|
+
font-variant-ligatures: none;
|
|
288
|
+
font-size: 0.75rem;
|
|
289
|
+
font-variant-numeric: tabular-nums;
|
|
290
|
+
color: rgb(var(--faint));
|
|
130
291
|
}
|
|
131
292
|
|
|
132
|
-
.
|
|
133
|
-
|
|
293
|
+
.message {
|
|
294
|
+
margin-top: 1.5rem;
|
|
295
|
+
padding: 1rem 1.25rem;
|
|
296
|
+
border: 1px solid rgb(var(--danger) / 0.35);
|
|
297
|
+
border-radius: 8px;
|
|
298
|
+
background-color: rgb(var(--danger-wash));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.message-label {
|
|
302
|
+
display: block;
|
|
303
|
+
margin-bottom: 0.375rem;
|
|
304
|
+
font-size: 0.8125rem;
|
|
134
305
|
font-weight: 600;
|
|
306
|
+
color: rgb(var(--danger));
|
|
135
307
|
}
|
|
136
308
|
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
309
|
+
.message-text {
|
|
310
|
+
font-family: var(--font-mono);
|
|
311
|
+
font-feature-settings: normal;
|
|
312
|
+
font-variant-ligatures: none;
|
|
313
|
+
font-size: 0.875rem;
|
|
314
|
+
line-height: 1.65;
|
|
315
|
+
white-space: pre-wrap;
|
|
316
|
+
overflow-wrap: anywhere;
|
|
317
|
+
color: rgb(var(--ink));
|
|
143
318
|
}
|
|
144
319
|
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
color: #888;
|
|
320
|
+
.facts {
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-wrap: wrap;
|
|
323
|
+
gap: 0.75rem 2.5rem;
|
|
324
|
+
margin-top: 1.25rem;
|
|
151
325
|
}
|
|
152
326
|
|
|
153
|
-
.
|
|
154
|
-
|
|
155
|
-
overflow-x: auto;
|
|
327
|
+
.fact {
|
|
328
|
+
min-width: 0;
|
|
156
329
|
}
|
|
157
330
|
|
|
158
|
-
.
|
|
331
|
+
.fact dt {
|
|
332
|
+
font-size: 0.8125rem;
|
|
333
|
+
color: rgb(var(--faint));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.fact dd {
|
|
337
|
+
margin-top: 0.125rem;
|
|
338
|
+
font-family: var(--font-mono);
|
|
339
|
+
font-feature-settings: normal;
|
|
340
|
+
font-variant-ligatures: none;
|
|
341
|
+
font-size: 0.8125rem;
|
|
342
|
+
font-variant-numeric: tabular-nums;
|
|
343
|
+
overflow-wrap: anywhere;
|
|
344
|
+
color: rgb(var(--ink));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* Sections */
|
|
348
|
+
|
|
349
|
+
.section {
|
|
350
|
+
margin-top: 2.5rem;
|
|
351
|
+
padding-top: 1.75rem;
|
|
352
|
+
border-top: 1px solid rgb(var(--line));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.section-title {
|
|
356
|
+
font-family: var(--font-display);
|
|
357
|
+
font-size: 1.125rem;
|
|
358
|
+
font-weight: 700;
|
|
359
|
+
font-stretch: 108%;
|
|
360
|
+
letter-spacing: -0.01em;
|
|
361
|
+
line-height: 1.25;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.section-title + * {
|
|
365
|
+
margin-top: 1rem;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* Code panel */
|
|
369
|
+
|
|
370
|
+
.panel {
|
|
371
|
+
overflow: hidden;
|
|
372
|
+
border: 1px solid rgb(var(--line));
|
|
373
|
+
border-radius: 8px;
|
|
374
|
+
background-color: rgb(var(--sunken));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.panel-bar {
|
|
159
378
|
display: flex;
|
|
160
379
|
align-items: center;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
380
|
+
justify-content: space-between;
|
|
381
|
+
gap: 1rem;
|
|
382
|
+
padding: 0.5rem 1rem;
|
|
383
|
+
border-bottom: 1px solid rgb(var(--line));
|
|
384
|
+
background-color: rgb(var(--canvas));
|
|
385
|
+
font-family: var(--font-mono);
|
|
386
|
+
font-feature-settings: normal;
|
|
387
|
+
font-variant-ligatures: none;
|
|
388
|
+
font-size: 0.75rem;
|
|
389
|
+
line-height: 1.5;
|
|
390
|
+
color: rgb(var(--faint));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.panel-file {
|
|
394
|
+
min-width: 0;
|
|
395
|
+
overflow-wrap: anywhere;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.panel-pos {
|
|
399
|
+
flex: none;
|
|
400
|
+
font-variant-numeric: tabular-nums;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.code {
|
|
404
|
+
overflow-x: auto;
|
|
405
|
+
padding: 0.75rem 0;
|
|
406
|
+
font-family: var(--font-mono);
|
|
407
|
+
font-feature-settings: normal;
|
|
408
|
+
font-variant-ligatures: none;
|
|
409
|
+
font-size: 0.8125rem;
|
|
410
|
+
line-height: 1.65;
|
|
411
|
+
tab-size: 2;
|
|
164
412
|
}
|
|
165
413
|
|
|
166
|
-
.code-
|
|
167
|
-
|
|
414
|
+
.code-lines {
|
|
415
|
+
min-width: 100%;
|
|
416
|
+
width: max-content;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.code-line {
|
|
420
|
+
display: flex;
|
|
421
|
+
padding-right: 1.25rem;
|
|
168
422
|
}
|
|
169
423
|
|
|
170
424
|
.line-number {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
425
|
+
flex: none;
|
|
426
|
+
width: 4.5ch;
|
|
427
|
+
margin-right: 1.25rem;
|
|
428
|
+
padding-left: 0.75rem;
|
|
174
429
|
text-align: right;
|
|
175
|
-
font-
|
|
430
|
+
font-variant-numeric: tabular-nums;
|
|
431
|
+
color: rgb(var(--faint));
|
|
432
|
+
user-select: none;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.line-content {
|
|
436
|
+
white-space: pre;
|
|
437
|
+
color: rgb(var(--muted));
|
|
176
438
|
}
|
|
177
439
|
|
|
178
440
|
.line-error {
|
|
179
|
-
background:
|
|
180
|
-
border-left: 4px solid #ff6b6b;
|
|
441
|
+
background-color: rgb(var(--danger) / 0.1);
|
|
181
442
|
}
|
|
182
443
|
|
|
183
444
|
.line-error .line-number {
|
|
184
|
-
color: #ff6b6b;
|
|
185
445
|
font-weight: 600;
|
|
446
|
+
color: rgb(var(--danger));
|
|
186
447
|
}
|
|
187
448
|
|
|
188
|
-
.line-
|
|
189
|
-
|
|
449
|
+
.line-error .line-content {
|
|
450
|
+
color: rgb(var(--ink));
|
|
190
451
|
}
|
|
191
452
|
|
|
453
|
+
/* Suggestions */
|
|
454
|
+
|
|
192
455
|
.suggestions {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
border-radius: 8px;
|
|
196
|
-
padding: 16px;
|
|
456
|
+
padding: 0;
|
|
457
|
+
list-style: none;
|
|
197
458
|
}
|
|
198
459
|
|
|
199
|
-
.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
460
|
+
.suggestion-item {
|
|
461
|
+
display: flex;
|
|
462
|
+
align-items: flex-start;
|
|
463
|
+
gap: 0.75rem;
|
|
464
|
+
padding: 0.75rem 0;
|
|
465
|
+
border-top: 1px solid rgb(var(--line));
|
|
466
|
+
color: rgb(var(--muted));
|
|
467
|
+
max-width: 48rem;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.suggestions .suggestion-item:first-child {
|
|
471
|
+
border-top-color: rgb(var(--ink));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.suggestion-icon {
|
|
475
|
+
width: 1rem;
|
|
476
|
+
height: 1rem;
|
|
477
|
+
flex: none;
|
|
478
|
+
margin-top: 0.3rem;
|
|
479
|
+
color: rgb(var(--faint));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Stack trace */
|
|
483
|
+
|
|
484
|
+
.stack summary {
|
|
203
485
|
display: flex;
|
|
204
486
|
align-items: center;
|
|
205
|
-
gap:
|
|
487
|
+
gap: 0.75rem;
|
|
488
|
+
width: fit-content;
|
|
489
|
+
cursor: pointer;
|
|
490
|
+
list-style: none;
|
|
491
|
+
border-radius: 4px;
|
|
206
492
|
}
|
|
207
493
|
|
|
208
|
-
.
|
|
209
|
-
|
|
210
|
-
border-radius: 6px;
|
|
211
|
-
padding: 12px;
|
|
212
|
-
margin-bottom: 8px;
|
|
213
|
-
border-left: 3px solid #34d399;
|
|
494
|
+
.stack summary::-webkit-details-marker {
|
|
495
|
+
display: none;
|
|
214
496
|
}
|
|
215
497
|
|
|
216
|
-
.
|
|
217
|
-
|
|
498
|
+
.stack-chevron {
|
|
499
|
+
width: 1rem;
|
|
500
|
+
height: 1rem;
|
|
501
|
+
flex: none;
|
|
502
|
+
color: rgb(var(--faint));
|
|
503
|
+
transition: transform 150ms;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.stack[open] .stack-chevron {
|
|
507
|
+
transform: rotate(90deg);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.stack-count {
|
|
511
|
+
font-family: var(--font-mono);
|
|
512
|
+
font-feature-settings: normal;
|
|
513
|
+
font-variant-ligatures: none;
|
|
514
|
+
font-size: 0.75rem;
|
|
515
|
+
font-variant-numeric: tabular-nums;
|
|
516
|
+
color: rgb(var(--faint));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.stack summary:hover .section-title {
|
|
520
|
+
color: rgb(var(--accent-ink));
|
|
218
521
|
}
|
|
219
522
|
|
|
220
523
|
.stack-trace {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
border-radius: 8px;
|
|
224
|
-
padding: 16px;
|
|
225
|
-
font-size: 13px;
|
|
226
|
-
line-height: 1.4;
|
|
524
|
+
margin-top: 1rem;
|
|
525
|
+
padding: 1rem 1.25rem;
|
|
227
526
|
overflow-x: auto;
|
|
228
|
-
|
|
229
|
-
|
|
527
|
+
border: 1px solid rgb(var(--line));
|
|
528
|
+
border-radius: 8px;
|
|
529
|
+
background-color: rgb(var(--sunken));
|
|
530
|
+
font-family: var(--font-mono);
|
|
531
|
+
font-feature-settings: normal;
|
|
532
|
+
font-variant-ligatures: none;
|
|
533
|
+
font-size: 0.8125rem;
|
|
534
|
+
line-height: 1.65;
|
|
535
|
+
white-space: pre;
|
|
536
|
+
color: rgb(var(--muted));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.stack-head {
|
|
540
|
+
color: rgb(var(--ink));
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.stack-dim {
|
|
544
|
+
color: rgb(var(--faint));
|
|
230
545
|
}
|
|
231
546
|
|
|
547
|
+
/* Footer */
|
|
548
|
+
|
|
232
549
|
.footer {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
display: flex;
|
|
237
|
-
justify-content: space-between;
|
|
238
|
-
align-items: center;
|
|
239
|
-
font-size: 14px;
|
|
240
|
-
color: #888;
|
|
550
|
+
border-top: 1px solid rgb(var(--line));
|
|
551
|
+
font-size: 0.8125rem;
|
|
552
|
+
color: rgb(var(--faint));
|
|
241
553
|
}
|
|
242
554
|
|
|
243
|
-
.footer
|
|
555
|
+
.footer .shell {
|
|
244
556
|
display: flex;
|
|
245
|
-
|
|
557
|
+
flex-wrap: wrap;
|
|
558
|
+
gap: 0.25rem 0.75rem;
|
|
559
|
+
padding-top: 1rem;
|
|
560
|
+
padding-bottom: 1rem;
|
|
246
561
|
}
|
|
247
562
|
|
|
248
|
-
.
|
|
249
|
-
|
|
250
|
-
color:
|
|
251
|
-
border: none;
|
|
252
|
-
padding: 8px 16px;
|
|
253
|
-
border-radius: 6px;
|
|
254
|
-
font-weight: 500;
|
|
255
|
-
cursor: pointer;
|
|
256
|
-
transition: background 0.2s;
|
|
563
|
+
.footer-name {
|
|
564
|
+
font-weight: 600;
|
|
565
|
+
color: rgb(var(--muted));
|
|
257
566
|
}
|
|
258
567
|
|
|
259
|
-
|
|
260
|
-
|
|
568
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
569
|
+
.main {
|
|
570
|
+
animation: overlay-in 240ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
@keyframes overlay-in {
|
|
574
|
+
from {
|
|
575
|
+
opacity: 0;
|
|
576
|
+
transform: translateY(6px);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
261
579
|
}
|
|
262
580
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
border-radius: 4px;
|
|
268
|
-
font-size: 12px;
|
|
269
|
-
font-weight: 600;
|
|
270
|
-
text-transform: uppercase;
|
|
271
|
-
letter-spacing: 0.5px;
|
|
581
|
+
@media (hover: none) {
|
|
582
|
+
.button kbd {
|
|
583
|
+
display: none;
|
|
584
|
+
}
|
|
272
585
|
}
|
|
273
586
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
587
|
+
@media (min-width: 640px) {
|
|
588
|
+
.shell {
|
|
589
|
+
padding-left: 1.5rem;
|
|
590
|
+
padding-right: 1.5rem;
|
|
591
|
+
}
|
|
278
592
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
padding: 16px;
|
|
593
|
+
.main {
|
|
594
|
+
padding-top: 3.5rem;
|
|
282
595
|
}
|
|
283
596
|
|
|
284
|
-
.
|
|
285
|
-
|
|
597
|
+
.headline {
|
|
598
|
+
font-size: 2.25rem;
|
|
286
599
|
}
|
|
287
600
|
|
|
288
|
-
.
|
|
289
|
-
font-size:
|
|
601
|
+
.code {
|
|
602
|
+
font-size: 0.875rem;
|
|
290
603
|
}
|
|
604
|
+
}
|
|
291
605
|
|
|
292
|
-
|
|
293
|
-
|
|
606
|
+
@media (min-width: 1024px) {
|
|
607
|
+
.shell {
|
|
608
|
+
padding-left: 3rem;
|
|
609
|
+
padding-right: 3rem;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.headline {
|
|
613
|
+
font-size: 2.75rem;
|
|
294
614
|
}
|
|
295
615
|
}
|
|
616
|
+
|
|
617
|
+
@media (max-width: 639px) {
|
|
618
|
+
.bar-actions {
|
|
619
|
+
width: 100%;
|
|
620
|
+
justify-content: space-between;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.button {
|
|
624
|
+
min-height: 2.75rem;
|
|
625
|
+
}
|
|
626
|
+
}`;
|
|
627
|
+
/**
|
|
628
|
+
* Live reload and refresh controls for the overlay page. The overlay is served
|
|
629
|
+
* instead of the page, so it needs its own connection to the dev server.
|
|
630
|
+
*/
|
|
631
|
+
const OVERLAY_SCRIPT = `
|
|
632
|
+
(function () {
|
|
633
|
+
var status = document.getElementById('stati-status');
|
|
634
|
+
var statusText = document.getElementById('stati-status-text');
|
|
635
|
+
var reloadButton = document.getElementById('stati-reload');
|
|
636
|
+
var shortcut = document.getElementById('stati-shortcut');
|
|
637
|
+
|
|
638
|
+
function reload() {
|
|
639
|
+
window.location.reload();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function setStatus(state, text) {
|
|
643
|
+
status.setAttribute('data-state', state);
|
|
644
|
+
statusText.textContent = text;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (/Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)) {
|
|
648
|
+
shortcut.textContent = '\\u2318R';
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
reloadButton.addEventListener('click', reload);
|
|
652
|
+
|
|
653
|
+
// Keyboard shortcut to refresh (Ctrl/Cmd + R)
|
|
654
|
+
document.addEventListener('keydown', function (e) {
|
|
655
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === 'r' || e.key === 'R')) {
|
|
656
|
+
e.preventDefault();
|
|
657
|
+
reload();
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// Auto-refresh when files change, over the dev server's WebSocket
|
|
662
|
+
if (typeof WebSocket === 'undefined') {
|
|
663
|
+
setStatus('off', 'Live reload unavailable');
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
var connectedBefore = false;
|
|
668
|
+
var retryDelay = 1000;
|
|
669
|
+
|
|
670
|
+
function connect() {
|
|
671
|
+
var ws;
|
|
672
|
+
try {
|
|
673
|
+
var protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
|
674
|
+
ws = new WebSocket(protocol + window.location.host + '/__ws');
|
|
675
|
+
} catch (e) {
|
|
676
|
+
setStatus('off', 'Live reload unavailable');
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
ws.onopen = function () {
|
|
681
|
+
// The server restarted while we were away; load the fresh build
|
|
682
|
+
if (connectedBefore) {
|
|
683
|
+
reload();
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
connectedBefore = true;
|
|
687
|
+
retryDelay = 1000;
|
|
688
|
+
setStatus('live', 'Watching for changes');
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
ws.onmessage = function (event) {
|
|
692
|
+
try {
|
|
693
|
+
if (JSON.parse(event.data).type === 'reload') {
|
|
694
|
+
reload();
|
|
695
|
+
}
|
|
696
|
+
} catch (e) {
|
|
697
|
+
// Ignore messages that are not JSON
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
ws.onclose = function () {
|
|
702
|
+
setStatus('off', 'Reconnecting to dev server');
|
|
703
|
+
setTimeout(connect, retryDelay);
|
|
704
|
+
retryDelay = Math.min(retryDelay * 2, 8000);
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
connect();
|
|
709
|
+
})();`;
|
|
710
|
+
/**
|
|
711
|
+
* Creates a styled error overlay HTML that displays comprehensive error information.
|
|
712
|
+
*
|
|
713
|
+
* @param error - The error details to display
|
|
714
|
+
* @param requestPath - The path that triggered the error
|
|
715
|
+
* @returns HTML string for the error overlay
|
|
716
|
+
*/
|
|
717
|
+
export function createErrorOverlay(error, requestPath) {
|
|
718
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
719
|
+
const position = error.line ? `${error.line}${error.column ? `:${error.column}` : ''}` : '';
|
|
720
|
+
return `<!DOCTYPE html>
|
|
721
|
+
<html lang="en">
|
|
722
|
+
<head>
|
|
723
|
+
<meta charset="UTF-8">
|
|
724
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
725
|
+
<meta name="color-scheme" content="light dark">
|
|
726
|
+
<link rel="icon" href="${FAVICON_HREF}">
|
|
727
|
+
<title>Stati Development Error</title>
|
|
728
|
+
<style>${OVERLAY_STYLES}
|
|
296
729
|
</style>
|
|
297
730
|
</head>
|
|
298
731
|
<body>
|
|
299
|
-
<
|
|
300
|
-
<div class="
|
|
301
|
-
<div class="
|
|
302
|
-
<
|
|
303
|
-
Stati
|
|
304
|
-
<span class="
|
|
732
|
+
<header class="bar">
|
|
733
|
+
<div class="shell">
|
|
734
|
+
<div class="brand">
|
|
735
|
+
<svg class="brand-mark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${BOLT_PATH}"/></svg>
|
|
736
|
+
<span class="brand-name">Stati</span>
|
|
737
|
+
<span class="brand-rule" aria-hidden="true"></span>
|
|
738
|
+
<span class="brand-context">Development error</span>
|
|
305
739
|
</div>
|
|
306
|
-
<div class="
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
740
|
+
<div class="bar-actions">
|
|
741
|
+
<span class="status" id="stati-status" data-state="connecting" role="status">
|
|
742
|
+
<span class="status-dot" aria-hidden="true"></span>
|
|
743
|
+
<span id="stati-status-text">Connecting to dev server</span>
|
|
744
|
+
</span>
|
|
745
|
+
<button type="button" class="button" id="stati-reload">
|
|
746
|
+
Refresh
|
|
747
|
+
<kbd id="stati-shortcut" aria-hidden="true">Ctrl+R</kbd>
|
|
748
|
+
</button>
|
|
314
749
|
</div>
|
|
315
750
|
</div>
|
|
751
|
+
</header>
|
|
752
|
+
|
|
753
|
+
<main class="main shell">
|
|
754
|
+
<section class="summary" aria-labelledby="stati-error-title">
|
|
755
|
+
<h1 class="headline" id="stati-error-title">${ERROR_HEADLINES[error.type]}</h1>
|
|
756
|
+
<p class="meta">
|
|
757
|
+
<span class="type-badge">${error.type}</span>
|
|
758
|
+
<time class="meta-time">${escapeHtml(timestamp)}</time>
|
|
759
|
+
</p>
|
|
760
|
+
|
|
761
|
+
<div class="message">
|
|
762
|
+
<span class="message-label">Error details</span>
|
|
763
|
+
<pre class="message-text">${escapeHtml(error.message)}</pre>
|
|
764
|
+
</div>
|
|
316
765
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
766
|
+
<dl class="facts">
|
|
767
|
+
${error.file
|
|
768
|
+
? `<div class="fact">
|
|
769
|
+
<dt>File</dt>
|
|
770
|
+
<dd>${escapeHtml(error.file)}</dd>
|
|
771
|
+
</div>`
|
|
772
|
+
: ''}
|
|
773
|
+
${position
|
|
774
|
+
? `<div class="fact">
|
|
775
|
+
<dt>Line</dt>
|
|
776
|
+
<dd>${position}</dd>
|
|
777
|
+
</div>`
|
|
778
|
+
: ''}
|
|
779
|
+
<div class="fact">
|
|
780
|
+
<dt>Route</dt>
|
|
781
|
+
<dd>${escapeHtml(requestPath)}</dd>
|
|
324
782
|
</div>
|
|
783
|
+
</dl>
|
|
784
|
+
</section>
|
|
325
785
|
|
|
326
|
-
|
|
786
|
+
${error.code || error.context
|
|
327
787
|
? `
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
</
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
? `<div class="location-item">
|
|
337
|
-
<span class="location-label">Line:</span>
|
|
338
|
-
<span class="location-value">${error.line}${error.column ? `:${error.column}` : ''}</span>
|
|
339
|
-
</div>`
|
|
788
|
+
<section class="section" aria-labelledby="stati-code-title">
|
|
789
|
+
<h2 class="section-title" id="stati-code-title">Code context</h2>
|
|
790
|
+
<figure class="panel">
|
|
791
|
+
${error.file
|
|
792
|
+
? `<figcaption class="panel-bar">
|
|
793
|
+
<span class="panel-file">${escapeHtml(error.file)}</span>
|
|
794
|
+
${position ? `<span class="panel-pos">${position}</span>` : ''}
|
|
795
|
+
</figcaption>`
|
|
340
796
|
: ''}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
<span class="location-value">${escapeHtml(requestPath)}</span>
|
|
344
|
-
</div>
|
|
797
|
+
<div class="code" tabindex="0" role="region" aria-label="Source code around the error">
|
|
798
|
+
<div class="code-lines">${formatCodeContext(error)}</div>
|
|
345
799
|
</div>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
${error.code || error.context
|
|
351
|
-
? `
|
|
352
|
-
<div class="error-section">
|
|
353
|
-
<div class="section-title">
|
|
354
|
-
📄 Code Context
|
|
355
|
-
</div>
|
|
356
|
-
<div class="code-block">
|
|
357
|
-
${error.file ? `<div class="code-header">${escapeHtml(error.file)}</div>` : ''}
|
|
358
|
-
<div class="code-content">
|
|
359
|
-
${formatCodeContext(error)}
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
362
|
-
</div>
|
|
363
|
-
`
|
|
800
|
+
</figure>
|
|
801
|
+
</section>
|
|
802
|
+
`
|
|
364
803
|
: ''}
|
|
365
804
|
|
|
366
|
-
|
|
805
|
+
${error.suggestions && error.suggestions.length > 0
|
|
367
806
|
? `
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
</div>
|
|
373
|
-
${error.suggestions
|
|
807
|
+
<section class="section" aria-labelledby="stati-suggestions-title">
|
|
808
|
+
<h2 class="section-title" id="stati-suggestions-title">Suggestions</h2>
|
|
809
|
+
<ul class="suggestions">
|
|
810
|
+
${error.suggestions
|
|
374
811
|
.map((suggestion) => `
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
812
|
+
<li class="suggestion-item">
|
|
813
|
+
<svg class="suggestion-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
|
814
|
+
<span>${escapeHtml(suggestion)}</span>
|
|
815
|
+
</li>`)
|
|
379
816
|
.join('')}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
817
|
+
</ul>
|
|
818
|
+
</section>
|
|
819
|
+
`
|
|
383
820
|
: ''}
|
|
384
821
|
|
|
385
|
-
|
|
822
|
+
${error.stack
|
|
386
823
|
? `
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
</
|
|
391
|
-
|
|
392
|
-
</
|
|
393
|
-
|
|
824
|
+
<details class="section stack" open>
|
|
825
|
+
<summary>
|
|
826
|
+
<svg class="stack-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 6l6 6-6 6"/></svg>
|
|
827
|
+
<h2 class="section-title">Stack trace</h2>
|
|
828
|
+
${formatFrameCount(error.stack)}
|
|
829
|
+
</summary>
|
|
830
|
+
<pre class="stack-trace">${formatStack(error.stack)}</pre>
|
|
831
|
+
</details>
|
|
832
|
+
`
|
|
394
833
|
: ''}
|
|
395
|
-
|
|
834
|
+
</main>
|
|
396
835
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<span>Fix the error and save to automatically reload</span>
|
|
402
|
-
</div>
|
|
403
|
-
<button class="refresh-button" onclick="window.location.reload()">
|
|
404
|
-
Refresh
|
|
405
|
-
</button>
|
|
836
|
+
<footer class="footer">
|
|
837
|
+
<div class="shell">
|
|
838
|
+
<span class="footer-name">Stati Development Server</span>
|
|
839
|
+
<span>Fix the error and save to automatically reload</span>
|
|
406
840
|
</div>
|
|
407
|
-
</
|
|
408
|
-
|
|
409
|
-
<script>
|
|
410
|
-
// Auto-refresh when files change (if WebSocket connection exists)
|
|
411
|
-
if (typeof WebSocket !== 'undefined') {
|
|
412
|
-
try {
|
|
413
|
-
const ws = new WebSocket('ws://localhost:3000/__ws');
|
|
414
|
-
ws.onmessage = function(event) {
|
|
415
|
-
const data = JSON.parse(event.data);
|
|
416
|
-
if (data.type === 'reload') {
|
|
417
|
-
window.location.reload();
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
} catch (e) {
|
|
421
|
-
// WebSocket connection failed, that's okay
|
|
422
|
-
}
|
|
423
|
-
}
|
|
841
|
+
</footer>
|
|
424
842
|
|
|
425
|
-
|
|
426
|
-
document.addEventListener('keydown', function(e) {
|
|
427
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'r') {
|
|
428
|
-
e.preventDefault();
|
|
429
|
-
window.location.reload();
|
|
430
|
-
}
|
|
431
|
-
});
|
|
843
|
+
<script>${OVERLAY_SCRIPT}
|
|
432
844
|
</script>
|
|
433
845
|
</body>
|
|
434
846
|
</html>`;
|
|
@@ -444,44 +856,67 @@ function escapeHtml(text) {
|
|
|
444
856
|
.replace(/"/g, '"')
|
|
445
857
|
.replace(/'/g, ''');
|
|
446
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Renders one numbered source line for the code context panel.
|
|
861
|
+
*/
|
|
862
|
+
function formatCodeLine(lineNumber, content, isError) {
|
|
863
|
+
return `<div class="code-line${isError ? ' line-error' : ''}"><span class="line-number">${lineNumber}${isError ? '<span class="sr-only"> (error)</span>' : ''}</span><span class="line-content">${escapeHtml(content)}</span></div>`;
|
|
864
|
+
}
|
|
447
865
|
/**
|
|
448
866
|
* Formats code context with line numbers and highlighting.
|
|
449
867
|
*/
|
|
450
868
|
function formatCodeContext(error) {
|
|
451
|
-
if (!error.context && !error.code) {
|
|
452
|
-
return '<div class="code-line"><span class="line-number">-</span>No code context available</div>';
|
|
453
|
-
}
|
|
454
869
|
const lines = [];
|
|
455
870
|
const errorLine = error.line || 0;
|
|
456
871
|
let currentLine = errorLine;
|
|
457
872
|
if (error.context?.before) {
|
|
458
873
|
currentLine = errorLine - error.context.before.length;
|
|
459
874
|
error.context.before.forEach((line) => {
|
|
460
|
-
lines.push(
|
|
461
|
-
<span class="line-number">${currentLine}</span>
|
|
462
|
-
<span class="line-content">${escapeHtml(line)}</span>
|
|
463
|
-
</div>`);
|
|
875
|
+
lines.push(formatCodeLine(currentLine, line, false));
|
|
464
876
|
currentLine++;
|
|
465
877
|
});
|
|
466
878
|
}
|
|
467
879
|
if (error.code) {
|
|
468
|
-
lines.push(
|
|
469
|
-
<span class="line-number">${errorLine}</span>
|
|
470
|
-
<span class="line-content">${escapeHtml(error.code)}</span>
|
|
471
|
-
</div>`);
|
|
880
|
+
lines.push(formatCodeLine(errorLine, error.code, true));
|
|
472
881
|
currentLine = errorLine + 1;
|
|
473
882
|
}
|
|
474
883
|
if (error.context?.after) {
|
|
475
884
|
error.context.after.forEach((line) => {
|
|
476
|
-
lines.push(
|
|
477
|
-
<span class="line-number">${currentLine}</span>
|
|
478
|
-
<span class="line-content">${escapeHtml(line)}</span>
|
|
479
|
-
</div>`);
|
|
885
|
+
lines.push(formatCodeLine(currentLine, line, false));
|
|
480
886
|
currentLine++;
|
|
481
887
|
});
|
|
482
888
|
}
|
|
483
889
|
return lines.join('');
|
|
484
890
|
}
|
|
891
|
+
const STACK_FRAME = /^\s+at\s/;
|
|
892
|
+
/**
|
|
893
|
+
* Formats a stack trace, keeping the first line prominent and dimming frames
|
|
894
|
+
* from dependencies and Node internals so the user's own frames stand out.
|
|
895
|
+
*/
|
|
896
|
+
function formatStack(stack) {
|
|
897
|
+
return stack
|
|
898
|
+
.split('\n')
|
|
899
|
+
.map((line, index) => {
|
|
900
|
+
const escaped = escapeHtml(line);
|
|
901
|
+
if (index === 0 && !STACK_FRAME.test(line)) {
|
|
902
|
+
return `<span class="stack-head">${escaped}</span>`;
|
|
903
|
+
}
|
|
904
|
+
if (/node_modules|\(node:|\bat node:/.test(line)) {
|
|
905
|
+
return `<span class="stack-dim">${escaped}</span>`;
|
|
906
|
+
}
|
|
907
|
+
return escaped;
|
|
908
|
+
})
|
|
909
|
+
.join('\n');
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Renders the frame count shown beside the stack trace heading.
|
|
913
|
+
*/
|
|
914
|
+
function formatFrameCount(stack) {
|
|
915
|
+
const frames = stack.split('\n').filter((line) => STACK_FRAME.test(line)).length;
|
|
916
|
+
return frames > 0
|
|
917
|
+
? `<span class="stack-count">${frames} ${frames === 1 ? 'frame' : 'frames'}</span>`
|
|
918
|
+
: '';
|
|
919
|
+
}
|
|
485
920
|
/**
|
|
486
921
|
* Parses error information from various error types to create structured ErrorDetails.
|
|
487
922
|
*/
|