@zenithbuild/runtime 0.6.13 → 0.7.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/dist/diagnostics.js +99 -5
- package/dist/events.js +5 -3
- package/dist/hydrate.d.ts +65 -5
- package/dist/hydrate.js +390 -79
- package/dist/template.js +52 -2
- package/package.json +1 -1
package/dist/diagnostics.js
CHANGED
|
@@ -2,6 +2,9 @@ const OVERLAY_ID = '__zenith_runtime_error_overlay';
|
|
|
2
2
|
const MAX_MESSAGE_LENGTH = 120;
|
|
3
3
|
const MAX_HINT_LENGTH = 140;
|
|
4
4
|
const MAX_PATH_LENGTH = 120;
|
|
5
|
+
const MAX_DOCS_LINK_LENGTH = 180;
|
|
6
|
+
const MAX_SNIPPET_LENGTH = 220;
|
|
7
|
+
const MAX_STACK_LENGTH = 420;
|
|
5
8
|
const VALID_PHASES = new Set(['hydrate', 'bind', 'render', 'event']);
|
|
6
9
|
const VALID_CODES = new Set([
|
|
7
10
|
'UNRESOLVED_EXPRESSION',
|
|
@@ -9,8 +12,20 @@ const VALID_CODES = new Set([
|
|
|
9
12
|
'MARKER_MISSING',
|
|
10
13
|
'FRAGMENT_MOUNT_FAILED',
|
|
11
14
|
'BINDING_APPLY_FAILED',
|
|
12
|
-
'EVENT_HANDLER_FAILED'
|
|
15
|
+
'EVENT_HANDLER_FAILED',
|
|
16
|
+
'COMPONENT_BOOTSTRAP_FAILED',
|
|
17
|
+
'UNSAFE_MEMBER_ACCESS'
|
|
13
18
|
]);
|
|
19
|
+
const DOCS_LINK_BY_CODE = Object.freeze({
|
|
20
|
+
UNRESOLVED_EXPRESSION: '/docs/documentation/reference/reactive-binding-model.md#expression-resolution',
|
|
21
|
+
NON_RENDERABLE_VALUE: '/docs/documentation/reference/reactive-binding-model.md#renderable-values',
|
|
22
|
+
MARKER_MISSING: '/docs/documentation/reference/markers.md',
|
|
23
|
+
FRAGMENT_MOUNT_FAILED: '/docs/documentation/contracts/runtime-contract.md#fragment-contract',
|
|
24
|
+
BINDING_APPLY_FAILED: '/docs/documentation/contracts/runtime-contract.md#binding-application',
|
|
25
|
+
EVENT_HANDLER_FAILED: '/docs/documentation/contracts/runtime-contract.md#event-bindings',
|
|
26
|
+
COMPONENT_BOOTSTRAP_FAILED: '/docs/documentation/contracts/runtime-contract.md#component-bootstrap',
|
|
27
|
+
UNSAFE_MEMBER_ACCESS: '/docs/documentation/reference/reactive-binding-model.md#expression-resolution'
|
|
28
|
+
});
|
|
14
29
|
function _truncate(input, maxLength) {
|
|
15
30
|
const text = String(input ?? '');
|
|
16
31
|
if (text.length <= maxLength)
|
|
@@ -48,6 +63,49 @@ function _sanitizePath(value) {
|
|
|
48
63
|
return undefined;
|
|
49
64
|
return _truncate(compact, MAX_PATH_LENGTH);
|
|
50
65
|
}
|
|
66
|
+
function _sanitizeDocsLink(value) {
|
|
67
|
+
if (value === null || value === undefined || value === false) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const compact = String(value).replace(/\s+/g, ' ').trim();
|
|
71
|
+
if (!compact)
|
|
72
|
+
return undefined;
|
|
73
|
+
return _truncate(compact, MAX_DOCS_LINK_LENGTH);
|
|
74
|
+
}
|
|
75
|
+
function _sanitizeSourceLocation(value) {
|
|
76
|
+
if (!value || typeof value !== 'object')
|
|
77
|
+
return undefined;
|
|
78
|
+
const line = Number(value.line);
|
|
79
|
+
const column = Number(value.column);
|
|
80
|
+
if (!Number.isInteger(line) || !Number.isInteger(column)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
if (line < 1 || column < 1) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return { line, column };
|
|
87
|
+
}
|
|
88
|
+
function _sanitizeSource(value) {
|
|
89
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const fileRaw = value.file;
|
|
93
|
+
const file = typeof fileRaw === 'string' ? _truncate(fileRaw.trim(), 240) : '';
|
|
94
|
+
if (!file) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const start = _sanitizeSourceLocation(value.start);
|
|
98
|
+
const end = _sanitizeSourceLocation(value.end);
|
|
99
|
+
const snippet = typeof value.snippet === 'string'
|
|
100
|
+
? _truncate(value.snippet.replace(/\s+/g, ' ').trim(), MAX_SNIPPET_LENGTH)
|
|
101
|
+
: undefined;
|
|
102
|
+
return {
|
|
103
|
+
file,
|
|
104
|
+
...(start ? { start } : null),
|
|
105
|
+
...(end ? { end } : null),
|
|
106
|
+
...(snippet ? { snippet } : null)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
51
109
|
function _normalizeMarker(marker) {
|
|
52
110
|
if (!marker || typeof marker !== 'object')
|
|
53
111
|
return undefined;
|
|
@@ -170,9 +228,25 @@ function _renderOverlay(payload) {
|
|
|
170
228
|
if (payload.path) {
|
|
171
229
|
textLines.push(`path: ${payload.path}`);
|
|
172
230
|
}
|
|
231
|
+
if (payload.source && payload.source.file) {
|
|
232
|
+
const line = payload.source.start?.line;
|
|
233
|
+
const column = payload.source.start?.column;
|
|
234
|
+
if (Number.isInteger(line) && Number.isInteger(column)) {
|
|
235
|
+
textLines.push(`source: ${payload.source.file}:${line}:${column}`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
textLines.push(`source: ${payload.source.file}`);
|
|
239
|
+
}
|
|
240
|
+
if (payload.source.snippet) {
|
|
241
|
+
textLines.push(`snippet: ${payload.source.snippet}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
173
244
|
if (payload.hint) {
|
|
174
245
|
textLines.push(`hint: ${payload.hint}`);
|
|
175
246
|
}
|
|
247
|
+
if (payload.docsLink) {
|
|
248
|
+
textLines.push(`docs: ${payload.docsLink}`);
|
|
249
|
+
}
|
|
176
250
|
const jsonText = _safeJson(payload);
|
|
177
251
|
const panelText = textLines.join('\n');
|
|
178
252
|
let pre = overlay.querySelector('pre[data-zx-runtime-error]');
|
|
@@ -204,7 +278,9 @@ function _mapLegacyError(error, fallback) {
|
|
|
204
278
|
message: _sanitizeMessage(fallback.message || safeMessage),
|
|
205
279
|
marker: _normalizeMarker(fallback.marker),
|
|
206
280
|
path: _sanitizePath(fallback.path),
|
|
207
|
-
hint: _sanitizeHint(fallback.hint)
|
|
281
|
+
hint: _sanitizeHint(fallback.hint),
|
|
282
|
+
source: _sanitizeSource(fallback.source),
|
|
283
|
+
docsLink: _sanitizeDocsLink(fallback.docsLink)
|
|
208
284
|
};
|
|
209
285
|
if (/failed to resolve expression literal/i.test(rawMessage)) {
|
|
210
286
|
details.phase = 'bind';
|
|
@@ -232,6 +308,9 @@ function _mapLegacyError(error, fallback) {
|
|
|
232
308
|
}
|
|
233
309
|
details.hint = details.hint || 'Confirm SSR markers and client selector tables match.';
|
|
234
310
|
}
|
|
311
|
+
if (!details.docsLink) {
|
|
312
|
+
details.docsLink = DOCS_LINK_BY_CODE[details.code];
|
|
313
|
+
}
|
|
235
314
|
return details;
|
|
236
315
|
}
|
|
237
316
|
export function isZenithRuntimeError(error) {
|
|
@@ -244,11 +323,13 @@ export function createZenithRuntimeError(details, cause) {
|
|
|
244
323
|
const phase = VALID_PHASES.has(details?.phase) ? details.phase : 'hydrate';
|
|
245
324
|
const code = VALID_CODES.has(details?.code) ? details.code : 'BINDING_APPLY_FAILED';
|
|
246
325
|
const message = _sanitizeMessage(details?.message || 'Runtime failure');
|
|
326
|
+
const docsLink = _sanitizeDocsLink(details?.docsLink || DOCS_LINK_BY_CODE[code]);
|
|
247
327
|
const payload = {
|
|
248
328
|
kind: 'ZENITH_RUNTIME_ERROR',
|
|
249
329
|
phase,
|
|
250
330
|
code,
|
|
251
|
-
message
|
|
331
|
+
message,
|
|
332
|
+
...(docsLink ? { docsLink } : null)
|
|
252
333
|
};
|
|
253
334
|
const marker = _normalizeMarker(details?.marker);
|
|
254
335
|
if (marker)
|
|
@@ -259,6 +340,13 @@ export function createZenithRuntimeError(details, cause) {
|
|
|
259
340
|
const hint = _sanitizeHint(details?.hint);
|
|
260
341
|
if (hint)
|
|
261
342
|
payload.hint = hint;
|
|
343
|
+
const source = _sanitizeSource(details?.source);
|
|
344
|
+
if (source)
|
|
345
|
+
payload.source = source;
|
|
346
|
+
const stack = _sanitizeHint(details?.stack);
|
|
347
|
+
if (stack) {
|
|
348
|
+
payload.stack = _truncate(stack, MAX_STACK_LENGTH);
|
|
349
|
+
}
|
|
262
350
|
const error = new Error(`[Zenith Runtime] ${code}: ${message}`);
|
|
263
351
|
error.name = 'ZenithRuntimeError';
|
|
264
352
|
error.zenithRuntimeError = payload;
|
|
@@ -293,12 +381,18 @@ export function rethrowZenithRuntimeError(error, fallback = {}) {
|
|
|
293
381
|
const marker = !payload.marker ? _normalizeMarker(fallback.marker) : payload.marker;
|
|
294
382
|
const path = !payload.path ? _sanitizePath(fallback.path) : payload.path;
|
|
295
383
|
const hint = !payload.hint ? _sanitizeHint(fallback.hint) : payload.hint;
|
|
296
|
-
|
|
384
|
+
const source = !payload.source ? _sanitizeSource(fallback.source) : payload.source;
|
|
385
|
+
const docsLink = !payload.docsLink
|
|
386
|
+
? _sanitizeDocsLink(fallback.docsLink || DOCS_LINK_BY_CODE[payload.code])
|
|
387
|
+
: payload.docsLink;
|
|
388
|
+
if (marker || path || hint || source || docsLink) {
|
|
297
389
|
updatedPayload = {
|
|
298
390
|
...payload,
|
|
299
391
|
...(marker ? { marker } : null),
|
|
300
392
|
...(path ? { path } : null),
|
|
301
|
-
...(hint ? { hint } : null)
|
|
393
|
+
...(hint ? { hint } : null),
|
|
394
|
+
...(source ? { source } : null),
|
|
395
|
+
...(docsLink ? { docsLink } : null)
|
|
302
396
|
};
|
|
303
397
|
error.zenithRuntimeError = updatedPayload;
|
|
304
398
|
error.toJSON = () => updatedPayload;
|
package/dist/events.js
CHANGED
|
@@ -29,10 +29,11 @@ export function bindEvent(element, eventName, exprFn) {
|
|
|
29
29
|
throwZenithRuntimeError({
|
|
30
30
|
phase: 'bind',
|
|
31
31
|
code: 'BINDING_APPLY_FAILED',
|
|
32
|
-
message: `Event binding
|
|
32
|
+
message: `Event binding expected a function reference for "${eventName}"`,
|
|
33
33
|
marker: { type: `data-zx-on-${eventName}`, id: '<unknown>' },
|
|
34
34
|
path: `event:${eventName}`,
|
|
35
|
-
hint: '
|
|
35
|
+
hint: 'Use on:*={handler} and ensure forwarded props are function-valued.',
|
|
36
|
+
docsLink: '/docs/documentation/contracts/runtime-contract.md#event-bindings'
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
const wrapped = function zenithBoundEvent(event) {
|
|
@@ -46,7 +47,8 @@ export function bindEvent(element, eventName, exprFn) {
|
|
|
46
47
|
message: `Event handler failed for "${eventName}"`,
|
|
47
48
|
marker: { type: `data-zx-on-${eventName}`, id: '<unknown>' },
|
|
48
49
|
path: `event:${eventName}:${resolved.name || '<anonymous>'}`,
|
|
49
|
-
hint: 'Inspect handler logic and referenced state.'
|
|
50
|
+
hint: 'Inspect handler logic and referenced state.',
|
|
51
|
+
docsLink: '/docs/documentation/contracts/runtime-contract.md#event-bindings'
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
};
|
package/dist/hydrate.d.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* @param {{
|
|
5
5
|
* ir_version: number,
|
|
6
6
|
* root: Document | Element,
|
|
7
|
-
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null }>,
|
|
8
|
-
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
|
|
9
|
-
* events: Array<{ index: number, event: string, selector: string }>,
|
|
10
|
-
* refs?: Array<{ index: number, state_index: number, selector: string }>,
|
|
7
|
+
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
8
|
+
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
9
|
+
* events: Array<{ index: number, event: string, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
10
|
+
* refs?: Array<{ index: number, state_index: number, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
11
11
|
* state_values: Array<*>,
|
|
12
12
|
* state_keys?: Array<string>,
|
|
13
13
|
* signals: Array<{ id: number, kind: 'signal', state_index: number }>,
|
|
14
|
-
* components?: Array<{ instance: string, selector: string, create: Function }>
|
|
14
|
+
* components?: Array<{ instance: string, selector: string, create: Function, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>
|
|
15
15
|
* }} payload
|
|
16
16
|
* @returns {() => void}
|
|
17
17
|
*/
|
|
@@ -25,22 +25,70 @@ export function hydrate(payload: {
|
|
|
25
25
|
component_instance?: string | null;
|
|
26
26
|
component_binding?: string | null;
|
|
27
27
|
literal?: string | null;
|
|
28
|
+
source?: {
|
|
29
|
+
file: string;
|
|
30
|
+
start?: {
|
|
31
|
+
line: number;
|
|
32
|
+
column: number;
|
|
33
|
+
};
|
|
34
|
+
end?: {
|
|
35
|
+
line: number;
|
|
36
|
+
column: number;
|
|
37
|
+
};
|
|
38
|
+
snippet?: string;
|
|
39
|
+
};
|
|
28
40
|
}>;
|
|
29
41
|
markers: Array<{
|
|
30
42
|
index: number;
|
|
31
43
|
kind: "text" | "attr" | "event";
|
|
32
44
|
selector: string;
|
|
33
45
|
attr?: string;
|
|
46
|
+
source?: {
|
|
47
|
+
file: string;
|
|
48
|
+
start?: {
|
|
49
|
+
line: number;
|
|
50
|
+
column: number;
|
|
51
|
+
};
|
|
52
|
+
end?: {
|
|
53
|
+
line: number;
|
|
54
|
+
column: number;
|
|
55
|
+
};
|
|
56
|
+
snippet?: string;
|
|
57
|
+
};
|
|
34
58
|
}>;
|
|
35
59
|
events: Array<{
|
|
36
60
|
index: number;
|
|
37
61
|
event: string;
|
|
38
62
|
selector: string;
|
|
63
|
+
source?: {
|
|
64
|
+
file: string;
|
|
65
|
+
start?: {
|
|
66
|
+
line: number;
|
|
67
|
+
column: number;
|
|
68
|
+
};
|
|
69
|
+
end?: {
|
|
70
|
+
line: number;
|
|
71
|
+
column: number;
|
|
72
|
+
};
|
|
73
|
+
snippet?: string;
|
|
74
|
+
};
|
|
39
75
|
}>;
|
|
40
76
|
refs?: Array<{
|
|
41
77
|
index: number;
|
|
42
78
|
state_index: number;
|
|
43
79
|
selector: string;
|
|
80
|
+
source?: {
|
|
81
|
+
file: string;
|
|
82
|
+
start?: {
|
|
83
|
+
line: number;
|
|
84
|
+
column: number;
|
|
85
|
+
};
|
|
86
|
+
end?: {
|
|
87
|
+
line: number;
|
|
88
|
+
column: number;
|
|
89
|
+
};
|
|
90
|
+
snippet?: string;
|
|
91
|
+
};
|
|
44
92
|
}>;
|
|
45
93
|
state_values: Array<any>;
|
|
46
94
|
state_keys?: Array<string>;
|
|
@@ -53,5 +101,17 @@ export function hydrate(payload: {
|
|
|
53
101
|
instance: string;
|
|
54
102
|
selector: string;
|
|
55
103
|
create: Function;
|
|
104
|
+
source?: {
|
|
105
|
+
file: string;
|
|
106
|
+
start?: {
|
|
107
|
+
line: number;
|
|
108
|
+
column: number;
|
|
109
|
+
};
|
|
110
|
+
end?: {
|
|
111
|
+
line: number;
|
|
112
|
+
column: number;
|
|
113
|
+
};
|
|
114
|
+
snippet?: string;
|
|
115
|
+
};
|
|
56
116
|
}>;
|
|
57
117
|
}): () => void;
|
package/dist/hydrate.js
CHANGED
|
@@ -15,26 +15,34 @@ const ALIAS_CONFLICT = Symbol('alias_conflict');
|
|
|
15
15
|
const ACTIVE_MARKER_CLASS = 'z-active';
|
|
16
16
|
const UNRESOLVED_LITERAL = Symbol('unresolved_literal');
|
|
17
17
|
const LEGACY_MARKUP_HELPER = 'html';
|
|
18
|
+
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
18
19
|
const BOOLEAN_ATTRIBUTES = new Set([
|
|
19
20
|
'disabled', 'checked', 'selected', 'readonly', 'multiple',
|
|
20
21
|
'hidden', 'autofocus', 'required', 'open'
|
|
21
22
|
]);
|
|
22
23
|
const STRICT_MEMBER_CHAIN_LITERAL_RE = /^(?:true|false|null|undefined|[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*)$/;
|
|
23
24
|
const UNSAFE_MEMBER_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
25
|
+
const DOCS_LINKS = Object.freeze({
|
|
26
|
+
eventBinding: '/docs/documentation/contracts/runtime-contract.md#event-bindings',
|
|
27
|
+
expressionScope: '/docs/documentation/reference/reactive-binding-model.md#expression-resolution',
|
|
28
|
+
markerTable: '/docs/documentation/reference/markers.md',
|
|
29
|
+
componentBootstrap: '/docs/documentation/contracts/runtime-contract.md#component-bootstrap',
|
|
30
|
+
refs: '/docs/documentation/reference/reactive-binding-model.md#refs-and-mount'
|
|
31
|
+
});
|
|
24
32
|
/**
|
|
25
33
|
* Hydrate a pre-rendered DOM tree using explicit payload tables.
|
|
26
34
|
*
|
|
27
35
|
* @param {{
|
|
28
36
|
* ir_version: number,
|
|
29
37
|
* root: Document | Element,
|
|
30
|
-
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null }>,
|
|
31
|
-
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
|
|
32
|
-
* events: Array<{ index: number, event: string, selector: string }>,
|
|
33
|
-
* refs?: Array<{ index: number, state_index: number, selector: string }>,
|
|
38
|
+
* expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
39
|
+
* markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
40
|
+
* events: Array<{ index: number, event: string, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
41
|
+
* refs?: Array<{ index: number, state_index: number, selector: string, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>,
|
|
34
42
|
* state_values: Array<*>,
|
|
35
43
|
* state_keys?: Array<string>,
|
|
36
44
|
* signals: Array<{ id: number, kind: 'signal', state_index: number }>,
|
|
37
|
-
* components?: Array<{ instance: string, selector: string, create: Function }>
|
|
45
|
+
* components?: Array<{ instance: string, selector: string, create: Function, source?: { file: string, start?: { line: number, column: number }, end?: { line: number, column: number }, snippet?: string } }>
|
|
38
46
|
* }} payload
|
|
39
47
|
* @returns {() => void}
|
|
40
48
|
*/
|
|
@@ -61,7 +69,7 @@ export function hydrate(payload) {
|
|
|
61
69
|
for (let i = 0; i < refs.length; i++) {
|
|
62
70
|
const refBinding = refs[i];
|
|
63
71
|
const targetRef = stateValues[refBinding.state_index];
|
|
64
|
-
const nodes = _resolveNodes(root, refBinding.selector, refBinding.index, 'ref');
|
|
72
|
+
const nodes = _resolveNodes(root, refBinding.selector, refBinding.index, 'ref', refBinding.source);
|
|
65
73
|
targetRef.current = nodes[0] || null;
|
|
66
74
|
hydratedRefs.push(targetRef);
|
|
67
75
|
}
|
|
@@ -78,38 +86,56 @@ export function hydrate(payload) {
|
|
|
78
86
|
component: component.instance,
|
|
79
87
|
route
|
|
80
88
|
}));
|
|
81
|
-
const hosts = _resolveNodes(root, component.selector, i, 'component');
|
|
89
|
+
const hosts = _resolveNodes(root, component.selector, i, 'component', component.source);
|
|
82
90
|
for (let j = 0; j < hosts.length; j++) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
try {
|
|
92
|
+
const componentScope = createSideEffectScope(`${component.instance}:${j}`);
|
|
93
|
+
const runtimeApi = {
|
|
94
|
+
signal,
|
|
95
|
+
state,
|
|
96
|
+
zeneffect(effect, dependenciesOrOptions) {
|
|
97
|
+
return zeneffect(effect, dependenciesOrOptions, componentScope);
|
|
98
|
+
},
|
|
99
|
+
zenEffect(effect, options) {
|
|
100
|
+
return zenEffect(effect, options, componentScope);
|
|
101
|
+
},
|
|
102
|
+
zenMount(callback) {
|
|
103
|
+
return zenMount(callback, componentScope);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const instance = component.create(hosts[j], resolvedProps, runtimeApi);
|
|
107
|
+
if (!instance || typeof instance !== 'object') {
|
|
108
|
+
throw new Error(`[Zenith Runtime] component factory for ${component.instance} must return an object`);
|
|
109
|
+
}
|
|
110
|
+
if (typeof instance.mount === 'function') {
|
|
111
|
+
instance.mount();
|
|
112
|
+
}
|
|
113
|
+
activateSideEffectScope(componentScope);
|
|
114
|
+
_registerDisposer(() => {
|
|
115
|
+
disposeSideEffectScope(componentScope);
|
|
116
|
+
if (typeof instance.destroy === 'function') {
|
|
117
|
+
instance.destroy();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
if (instance.bindings && typeof instance.bindings === 'object') {
|
|
121
|
+
componentBindings[component.instance] = instance.bindings;
|
|
95
122
|
}
|
|
96
|
-
};
|
|
97
|
-
const instance = component.create(hosts[j], resolvedProps, runtimeApi);
|
|
98
|
-
if (!instance || typeof instance !== 'object') {
|
|
99
|
-
throw new Error(`[Zenith Runtime] component factory for ${component.instance} must return an object`);
|
|
100
|
-
}
|
|
101
|
-
if (typeof instance.mount === 'function') {
|
|
102
|
-
instance.mount();
|
|
103
123
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
124
|
+
catch (error) {
|
|
125
|
+
try {
|
|
126
|
+
rethrowZenithRuntimeError(error, {
|
|
127
|
+
phase: 'hydrate',
|
|
128
|
+
code: 'COMPONENT_BOOTSTRAP_FAILED',
|
|
129
|
+
message: `Component bootstrap failed for "${component.instance}"`,
|
|
130
|
+
path: `component[${component.instance}]`,
|
|
131
|
+
hint: 'Fix the failing component and refresh; other components continue mounting.',
|
|
132
|
+
docsLink: DOCS_LINKS.componentBootstrap,
|
|
133
|
+
source: component.source
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Fault containment: continue mounting remaining components.
|
|
109
138
|
}
|
|
110
|
-
});
|
|
111
|
-
if (instance.bindings && typeof instance.bindings === 'object') {
|
|
112
|
-
componentBindings[component.instance] = instance.bindings;
|
|
113
139
|
}
|
|
114
140
|
}
|
|
115
141
|
}
|
|
@@ -134,10 +160,15 @@ export function hydrate(payload) {
|
|
|
134
160
|
if (marker.kind === 'event') {
|
|
135
161
|
continue;
|
|
136
162
|
}
|
|
137
|
-
const nodes = _resolveNodes(root, marker.selector, marker.index, marker.kind);
|
|
163
|
+
const nodes = _resolveNodes(root, marker.selector, marker.index, marker.kind, marker.source);
|
|
138
164
|
markerNodesByIndex.set(marker.index, nodes);
|
|
139
|
-
|
|
140
|
-
|
|
165
|
+
try {
|
|
166
|
+
const value = _evaluateExpression(expressions[marker.index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns, marker, null);
|
|
167
|
+
_applyMarkerValue(nodes, marker, value);
|
|
168
|
+
}
|
|
169
|
+
catch (evalErr) {
|
|
170
|
+
throw evalErr;
|
|
171
|
+
}
|
|
141
172
|
}
|
|
142
173
|
for (let i = 0; i < expressions.length; i++) {
|
|
143
174
|
if (!markerIndices.has(i)) {
|
|
@@ -149,9 +180,9 @@ export function hydrate(payload) {
|
|
|
149
180
|
if (!marker || marker.kind === 'event') {
|
|
150
181
|
return;
|
|
151
182
|
}
|
|
152
|
-
const nodes = markerNodesByIndex.get(index) || _resolveNodes(root, marker.selector, marker.index, marker.kind);
|
|
183
|
+
const nodes = markerNodesByIndex.get(index) || _resolveNodes(root, marker.selector, marker.index, marker.kind, marker.source);
|
|
153
184
|
markerNodesByIndex.set(index, nodes);
|
|
154
|
-
const value = _evaluateExpression(expressions[index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns);
|
|
185
|
+
const value = _evaluateExpression(expressions[index], stateValues, stateKeys, signalMap, componentBindings, params, ssrData, marker.kind, props, exprFns, marker, null);
|
|
155
186
|
_applyMarkerValue(nodes, marker, value);
|
|
156
187
|
}
|
|
157
188
|
const dependentMarkersBySignal = new Map();
|
|
@@ -225,16 +256,21 @@ export function hydrate(payload) {
|
|
|
225
256
|
throw new Error(`[Zenith Runtime] duplicate event index ${eventBinding.index}`);
|
|
226
257
|
}
|
|
227
258
|
eventIndices.add(eventBinding.index);
|
|
228
|
-
const
|
|
229
|
-
const
|
|
259
|
+
const marker = markerByIndex.get(eventBinding.index) || null;
|
|
260
|
+
const nodes = _resolveNodes(root, eventBinding.selector, eventBinding.index, 'event', eventBinding.source || marker?.source);
|
|
261
|
+
const expressionBinding = expressions[eventBinding.index];
|
|
262
|
+
const handler = _evaluateExpression(expressionBinding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, 'event', props || {}, exprFns, marker, eventBinding);
|
|
230
263
|
if (typeof handler !== 'function') {
|
|
264
|
+
const passedExpression = _describeBindingExpression(expressionBinding);
|
|
231
265
|
throwZenithRuntimeError({
|
|
232
266
|
phase: 'bind',
|
|
233
267
|
code: 'BINDING_APPLY_FAILED',
|
|
234
|
-
message: `Event binding at index ${eventBinding.index}
|
|
268
|
+
message: `Event binding at index ${eventBinding.index} expected a function reference. You passed: ${passedExpression}`,
|
|
235
269
|
marker: { type: `data-zx-on-${eventBinding.event}`, id: eventBinding.index },
|
|
236
270
|
path: `event[${eventBinding.index}].${eventBinding.event}`,
|
|
237
|
-
hint: '
|
|
271
|
+
hint: 'Use on:*={handler} or ensure the forwarded prop is a function.',
|
|
272
|
+
docsLink: DOCS_LINKS.eventBinding,
|
|
273
|
+
source: _resolveBindingSource(expressionBinding, marker, eventBinding)
|
|
238
274
|
});
|
|
239
275
|
}
|
|
240
276
|
for (let j = 0; j < nodes.length; j++) {
|
|
@@ -250,7 +286,9 @@ export function hydrate(payload) {
|
|
|
250
286
|
message: `Event handler failed for "${eventBinding.event}"`,
|
|
251
287
|
marker: { type: `data-zx-on-${eventBinding.event}`, id: eventBinding.index },
|
|
252
288
|
path: `event[${eventBinding.index}].${eventBinding.event}`,
|
|
253
|
-
hint: 'Inspect the handler body and referenced state.'
|
|
289
|
+
hint: 'Inspect the handler body and referenced state.',
|
|
290
|
+
docsLink: DOCS_LINKS.eventBinding,
|
|
291
|
+
source: _resolveBindingSource(expressionBinding, marker, eventBinding)
|
|
254
292
|
});
|
|
255
293
|
}
|
|
256
294
|
};
|
|
@@ -314,7 +352,8 @@ export function hydrate(payload) {
|
|
|
314
352
|
rethrowZenithRuntimeError(error, {
|
|
315
353
|
phase: 'hydrate',
|
|
316
354
|
code: 'BINDING_APPLY_FAILED',
|
|
317
|
-
hint: 'Inspect marker tables, expression bindings, and the runtime overlay diagnostics.'
|
|
355
|
+
hint: 'Inspect marker tables, expression bindings, and the runtime overlay diagnostics.',
|
|
356
|
+
docsLink: DOCS_LINKS.markerTable
|
|
318
357
|
});
|
|
319
358
|
}
|
|
320
359
|
}
|
|
@@ -390,6 +429,7 @@ function _validatePayload(payload) {
|
|
|
390
429
|
throw new Error(`[Zenith Runtime] expression at position ${i} has invalid fn_index`);
|
|
391
430
|
}
|
|
392
431
|
}
|
|
432
|
+
_assertValidSourceSpan(expression.source, `expression[${i}]`);
|
|
393
433
|
if (expression.signal_indices !== undefined) {
|
|
394
434
|
if (!Array.isArray(expression.signal_indices)) {
|
|
395
435
|
throw new Error(`[Zenith Runtime] expression at position ${i} must provide signal_indices[]`);
|
|
@@ -421,6 +461,7 @@ function _validatePayload(payload) {
|
|
|
421
461
|
if (marker.kind === 'attr' && (typeof marker.attr !== 'string' || marker.attr.length === 0)) {
|
|
422
462
|
throw new Error(`[Zenith Runtime] attr marker at position ${i} requires attr name`);
|
|
423
463
|
}
|
|
464
|
+
_assertValidSourceSpan(marker.source, `marker[${i}]`);
|
|
424
465
|
}
|
|
425
466
|
for (let i = 0; i < events.length; i++) {
|
|
426
467
|
const eventBinding = events[i];
|
|
@@ -436,6 +477,7 @@ function _validatePayload(payload) {
|
|
|
436
477
|
if (typeof eventBinding.selector !== 'string' || eventBinding.selector.length === 0) {
|
|
437
478
|
throw new Error(`[Zenith Runtime] event binding at position ${i} requires selector`);
|
|
438
479
|
}
|
|
480
|
+
_assertValidSourceSpan(eventBinding.source, `event[${i}]`);
|
|
439
481
|
}
|
|
440
482
|
for (let i = 0; i < refs.length; i++) {
|
|
441
483
|
const refBinding = refs[i];
|
|
@@ -453,6 +495,7 @@ function _validatePayload(payload) {
|
|
|
453
495
|
if (typeof refBinding.selector !== 'string' || refBinding.selector.length === 0) {
|
|
454
496
|
throw new Error(`[Zenith Runtime] ref binding at position ${i} requires selector`);
|
|
455
497
|
}
|
|
498
|
+
_assertValidSourceSpan(refBinding.source, `ref[${i}]`);
|
|
456
499
|
const candidate = stateValues[refBinding.state_index];
|
|
457
500
|
if (!candidate || typeof candidate !== 'object' || !Object.prototype.hasOwnProperty.call(candidate, 'current')) {
|
|
458
501
|
throw new Error(`[Zenith Runtime] ref binding at position ${i} must resolve to a ref-like object`);
|
|
@@ -490,6 +533,7 @@ function _validatePayload(payload) {
|
|
|
490
533
|
if (typeof component.create !== 'function') {
|
|
491
534
|
throw new Error(`[Zenith Runtime] component at position ${i} requires create() function`);
|
|
492
535
|
}
|
|
536
|
+
_assertValidSourceSpan(component.source, `component[${i}]`);
|
|
493
537
|
}
|
|
494
538
|
if (payload.params !== undefined) {
|
|
495
539
|
if (!payload.params || typeof payload.params !== 'object' || Array.isArray(payload.params)) {
|
|
@@ -554,6 +598,36 @@ function _validatePayload(payload) {
|
|
|
554
598
|
};
|
|
555
599
|
return Object.freeze(validatedPayload);
|
|
556
600
|
}
|
|
601
|
+
function _assertValidSourceSpan(source, contextLabel) {
|
|
602
|
+
if (source === undefined || source === null) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
606
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source must be an object`);
|
|
607
|
+
}
|
|
608
|
+
if (typeof source.file !== 'string' || source.file.length === 0) {
|
|
609
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.file must be a non-empty string`);
|
|
610
|
+
}
|
|
611
|
+
const points = ['start', 'end'];
|
|
612
|
+
for (let i = 0; i < points.length; i++) {
|
|
613
|
+
const point = source[points[i]];
|
|
614
|
+
if (point === undefined || point === null) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (!point || typeof point !== 'object' || Array.isArray(point)) {
|
|
618
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]} must be an object`);
|
|
619
|
+
}
|
|
620
|
+
if (!Number.isInteger(point.line) || point.line < 1) {
|
|
621
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]}.line must be >= 1`);
|
|
622
|
+
}
|
|
623
|
+
if (!Number.isInteger(point.column) || point.column < 1) {
|
|
624
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.${points[i]}.column must be >= 1`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (source.snippet !== undefined && source.snippet !== null && typeof source.snippet !== 'string') {
|
|
628
|
+
throw new Error(`[Zenith Runtime] ${contextLabel}.source.snippet must be a string when provided`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
557
631
|
function _resolveComponentProps(propTable, signalMap, context = {}) {
|
|
558
632
|
if (!Array.isArray(propTable)) {
|
|
559
633
|
throw new Error('[Zenith Runtime] component props must be an array');
|
|
@@ -592,20 +666,44 @@ function _resolveComponentProps(propTable, signalMap, context = {}) {
|
|
|
592
666
|
}
|
|
593
667
|
return resolved;
|
|
594
668
|
}
|
|
595
|
-
function _resolveNodes(root, selector, index, kind) {
|
|
596
|
-
const nodes =
|
|
669
|
+
function _resolveNodes(root, selector, index, kind, source = undefined) {
|
|
670
|
+
const nodes = selector.startsWith('comment:')
|
|
671
|
+
? _resolveCommentNodes(root, selector.slice('comment:'.length))
|
|
672
|
+
: root.querySelectorAll(selector);
|
|
597
673
|
if (!nodes || nodes.length === 0) {
|
|
674
|
+
const isRef = kind === 'ref';
|
|
598
675
|
throwZenithRuntimeError({
|
|
599
676
|
phase: 'bind',
|
|
600
677
|
code: 'MARKER_MISSING',
|
|
601
678
|
message: `Unresolved ${kind} marker index ${index}`,
|
|
602
679
|
marker: { type: kind, id: index },
|
|
603
680
|
path: `selector:${selector}`,
|
|
604
|
-
hint:
|
|
681
|
+
hint: isRef
|
|
682
|
+
? 'Use ref + zenMount and ensure the ref is bound in markup before mount.'
|
|
683
|
+
: 'Confirm SSR marker attributes and runtime selector tables match.',
|
|
684
|
+
docsLink: isRef ? DOCS_LINKS.refs : DOCS_LINKS.markerTable,
|
|
685
|
+
source
|
|
605
686
|
});
|
|
606
687
|
}
|
|
607
688
|
return nodes;
|
|
608
689
|
}
|
|
690
|
+
function _resolveCommentNodes(root, markerText) {
|
|
691
|
+
const walkerRoot = root && root.nodeType === 9 && root.documentElement ? root.documentElement : root;
|
|
692
|
+
const doc = walkerRoot && walkerRoot.ownerDocument ? walkerRoot.ownerDocument : walkerRoot;
|
|
693
|
+
if (!walkerRoot || !doc || typeof doc.createTreeWalker !== 'function') {
|
|
694
|
+
return [];
|
|
695
|
+
}
|
|
696
|
+
const nodes = [];
|
|
697
|
+
const walker = doc.createTreeWalker(walkerRoot, NodeFilter.SHOW_COMMENT);
|
|
698
|
+
let current = walker.nextNode();
|
|
699
|
+
while (current) {
|
|
700
|
+
if (current.data === markerText) {
|
|
701
|
+
nodes.push(current);
|
|
702
|
+
}
|
|
703
|
+
current = walker.nextNode();
|
|
704
|
+
}
|
|
705
|
+
return nodes;
|
|
706
|
+
}
|
|
609
707
|
function _resolveExpressionSignalIndices(binding) {
|
|
610
708
|
if (!binding || typeof binding !== 'object') {
|
|
611
709
|
return [];
|
|
@@ -618,25 +716,30 @@ function _resolveExpressionSignalIndices(binding) {
|
|
|
618
716
|
}
|
|
619
717
|
return [];
|
|
620
718
|
}
|
|
621
|
-
function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns) {
|
|
719
|
+
function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props, exprFns, markerBinding = null, eventBinding = null) {
|
|
622
720
|
if (binding.fn_index != null && binding.fn_index !== undefined) {
|
|
623
721
|
const fns = Array.isArray(exprFns) ? exprFns : [];
|
|
624
722
|
const fn = fns[binding.fn_index];
|
|
625
723
|
if (typeof fn === 'function') {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
724
|
+
try {
|
|
725
|
+
return fn({
|
|
726
|
+
signalMap,
|
|
727
|
+
params,
|
|
728
|
+
ssrData,
|
|
729
|
+
props: props || {},
|
|
730
|
+
componentBindings,
|
|
731
|
+
zenhtml: _zenhtml,
|
|
732
|
+
fragment(html) {
|
|
733
|
+
return {
|
|
734
|
+
__zenith_fragment: true,
|
|
735
|
+
html: html === null || html === undefined || html === false ? '' : String(html)
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
catch (fnErr) {
|
|
741
|
+
throw fnErr;
|
|
742
|
+
}
|
|
640
743
|
}
|
|
641
744
|
}
|
|
642
745
|
if (binding.signal_index !== null && binding.signal_index !== undefined) {
|
|
@@ -678,7 +781,7 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
678
781
|
if (binding.literal !== null && binding.literal !== undefined) {
|
|
679
782
|
if (typeof binding.literal === 'string') {
|
|
680
783
|
const trimmedLiteral = binding.literal.trim();
|
|
681
|
-
const strictMemberValue = _resolveStrictMemberChainLiteral(trimmedLiteral, stateValues, stateKeys, params, ssrData, mode, props, binding.marker_index);
|
|
784
|
+
const strictMemberValue = _resolveStrictMemberChainLiteral(trimmedLiteral, stateValues, stateKeys, params, ssrData, mode, props, binding.marker_index, _resolveBindingSource(binding, markerBinding, eventBinding));
|
|
682
785
|
if (strictMemberValue !== UNRESOLVED_LITERAL) {
|
|
683
786
|
return strictMemberValue;
|
|
684
787
|
}
|
|
@@ -696,16 +799,23 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
696
799
|
return primitiveValue;
|
|
697
800
|
}
|
|
698
801
|
if (_isLikelyExpressionLiteral(trimmedLiteral)) {
|
|
802
|
+
const missingIdentifier = _extractMissingIdentifier(trimmedLiteral);
|
|
699
803
|
throwZenithRuntimeError({
|
|
700
804
|
phase: 'bind',
|
|
701
805
|
code: 'UNRESOLVED_EXPRESSION',
|
|
702
|
-
message:
|
|
806
|
+
message: missingIdentifier
|
|
807
|
+
? `Unresolved expression identifier "${missingIdentifier}" in ${_truncateLiteralForError(trimmedLiteral)}`
|
|
808
|
+
: `Failed to resolve expression literal: ${_truncateLiteralForError(trimmedLiteral)}`,
|
|
703
809
|
marker: {
|
|
704
810
|
type: _markerTypeForError(mode),
|
|
705
811
|
id: binding.marker_index
|
|
706
812
|
},
|
|
707
813
|
path: `expression[${binding.marker_index}]`,
|
|
708
|
-
hint:
|
|
814
|
+
hint: missingIdentifier
|
|
815
|
+
? `Declare "${missingIdentifier}" in scope or pass it via props.`
|
|
816
|
+
: 'Declare the missing identifier in scope or pass it via props.',
|
|
817
|
+
docsLink: DOCS_LINKS.expressionScope,
|
|
818
|
+
source: _resolveBindingSource(binding, markerBinding, eventBinding)
|
|
709
819
|
});
|
|
710
820
|
}
|
|
711
821
|
}
|
|
@@ -713,7 +823,7 @@ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, compone
|
|
|
713
823
|
}
|
|
714
824
|
return '';
|
|
715
825
|
}
|
|
716
|
-
function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix, hint) {
|
|
826
|
+
function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix, hint, source) {
|
|
717
827
|
throwZenithRuntimeError({
|
|
718
828
|
phase: 'bind',
|
|
719
829
|
code: 'UNRESOLVED_EXPRESSION',
|
|
@@ -723,10 +833,12 @@ function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix
|
|
|
723
833
|
id: markerIndex
|
|
724
834
|
},
|
|
725
835
|
path: `marker[${markerIndex}].${pathSuffix}`,
|
|
726
|
-
hint
|
|
836
|
+
hint,
|
|
837
|
+
docsLink: DOCS_LINKS.expressionScope,
|
|
838
|
+
source
|
|
727
839
|
});
|
|
728
840
|
}
|
|
729
|
-
function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, params, ssrData, mode, props, markerIndex) {
|
|
841
|
+
function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, params, ssrData, mode, props, markerIndex, source) {
|
|
730
842
|
if (typeof literal !== 'string' || !STRICT_MEMBER_CHAIN_LITERAL_RE.test(literal)) {
|
|
731
843
|
return UNRESOLVED_LITERAL;
|
|
732
844
|
}
|
|
@@ -742,7 +854,7 @@ function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, param
|
|
|
742
854
|
const baseIdentifier = segments[0];
|
|
743
855
|
const scope = _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props);
|
|
744
856
|
if (!Object.prototype.hasOwnProperty.call(scope, baseIdentifier)) {
|
|
745
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${baseIdentifier}`, `Base identifier "${baseIdentifier}" is not bound. Check props/data/params and declared state keys
|
|
857
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${baseIdentifier}`, `Base identifier "${baseIdentifier}" is not bound. Check props/data/params and declared state keys.`, source);
|
|
746
858
|
}
|
|
747
859
|
let cursor = scope[baseIdentifier];
|
|
748
860
|
let traversedPath = baseIdentifier;
|
|
@@ -754,18 +866,20 @@ function _resolveStrictMemberChainLiteral(literal, stateValues, stateKeys, param
|
|
|
754
866
|
code: 'UNSAFE_MEMBER_ACCESS',
|
|
755
867
|
message: `Blocked unsafe member access: ${segment} in path "${literal}"`,
|
|
756
868
|
path: `marker[${markerIndex}].expression.${literal}`,
|
|
757
|
-
hint: 'Property access to __proto__, prototype, and constructor is forbidden.'
|
|
869
|
+
hint: 'Property access to __proto__, prototype, and constructor is forbidden.',
|
|
870
|
+
docsLink: DOCS_LINKS.expressionScope,
|
|
871
|
+
source
|
|
758
872
|
});
|
|
759
873
|
}
|
|
760
874
|
if (cursor === null || cursor === undefined) {
|
|
761
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it is null or undefined
|
|
875
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it is null or undefined.`, source);
|
|
762
876
|
}
|
|
763
877
|
const cursorType = typeof cursor;
|
|
764
878
|
if (cursorType !== 'object' && cursorType !== 'function') {
|
|
765
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it resolved to a ${cursorType}
|
|
879
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Cannot read "${segment}" from ${traversedPath} because it resolved to a ${cursorType}.`, source);
|
|
766
880
|
}
|
|
767
881
|
if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
|
|
768
|
-
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Missing member "${segment}" on ${traversedPath}. Check your bindings
|
|
882
|
+
_throwUnresolvedMemberChainError(literal, markerIndex, mode, `expression.${traversedPath}.${segment}`, `Missing member "${segment}" on ${traversedPath}. Check your bindings.`, source);
|
|
769
883
|
}
|
|
770
884
|
cursor = cursor[segment];
|
|
771
885
|
traversedPath = `${traversedPath}.${segment}`;
|
|
@@ -898,6 +1012,52 @@ function _isLikelyExpressionLiteral(literal) {
|
|
|
898
1012
|
}
|
|
899
1013
|
return /=>|[()[\]{}<>=?:.+\-*/%|&!]/.test(trimmed);
|
|
900
1014
|
}
|
|
1015
|
+
function _extractMissingIdentifier(literal) {
|
|
1016
|
+
if (typeof literal !== 'string') {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const match = literal.trim().match(/^([A-Za-z_$][A-Za-z0-9_$]*)/);
|
|
1020
|
+
if (!match) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
const candidate = match[1];
|
|
1024
|
+
if (candidate === 'true' || candidate === 'false' || candidate === 'null' || candidate === 'undefined') {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
return candidate;
|
|
1028
|
+
}
|
|
1029
|
+
function _resolveBindingSource(binding, markerBinding, eventBinding) {
|
|
1030
|
+
const candidates = [
|
|
1031
|
+
binding?.source,
|
|
1032
|
+
eventBinding?.source,
|
|
1033
|
+
markerBinding?.source
|
|
1034
|
+
];
|
|
1035
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
1036
|
+
const candidate = candidates[i];
|
|
1037
|
+
if (candidate && typeof candidate === 'object' && typeof candidate.file === 'string') {
|
|
1038
|
+
return candidate;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return undefined;
|
|
1042
|
+
}
|
|
1043
|
+
function _describeBindingExpression(binding) {
|
|
1044
|
+
if (!binding || typeof binding !== 'object') {
|
|
1045
|
+
return '<unknown>';
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof binding.literal === 'string' && binding.literal.trim().length > 0) {
|
|
1048
|
+
return _truncateLiteralForError(binding.literal.trim());
|
|
1049
|
+
}
|
|
1050
|
+
if (Number.isInteger(binding.state_index)) {
|
|
1051
|
+
return `state[${binding.state_index}]`;
|
|
1052
|
+
}
|
|
1053
|
+
if (Number.isInteger(binding.signal_index)) {
|
|
1054
|
+
return `signal[${binding.signal_index}]`;
|
|
1055
|
+
}
|
|
1056
|
+
if (typeof binding.component_instance === 'string' && typeof binding.component_binding === 'string') {
|
|
1057
|
+
return `${binding.component_instance}.${binding.component_binding}`;
|
|
1058
|
+
}
|
|
1059
|
+
return '<unknown expression>';
|
|
1060
|
+
}
|
|
901
1061
|
function _markerTypeForError(kind) {
|
|
902
1062
|
if (kind === 'text')
|
|
903
1063
|
return 'data-zx-e';
|
|
@@ -1308,6 +1468,10 @@ function _applyMarkerValue(nodes, marker, value) {
|
|
|
1308
1468
|
try {
|
|
1309
1469
|
const node = nodes[i];
|
|
1310
1470
|
if (marker.kind === 'text') {
|
|
1471
|
+
if (node && node.nodeType === 8) {
|
|
1472
|
+
_applyCommentMarkerValue(node, value, `${markerPath}.text`);
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1311
1475
|
if (_isStructuralFragment(value)) {
|
|
1312
1476
|
_mountStructuralFragment(node, value, `${markerPath}.text`);
|
|
1313
1477
|
continue;
|
|
@@ -1337,11 +1501,40 @@ function _applyMarkerValue(nodes, marker, value) {
|
|
|
1337
1501
|
path: marker.kind === 'attr'
|
|
1338
1502
|
? `${markerPath}.attr.${marker.attr}`
|
|
1339
1503
|
: `${markerPath}.${marker.kind}`,
|
|
1340
|
-
hint: 'Check the binding value type and marker mapping.'
|
|
1504
|
+
hint: 'Check the binding value type and marker mapping.',
|
|
1505
|
+
docsLink: DOCS_LINKS.markerTable,
|
|
1506
|
+
source: marker.source
|
|
1341
1507
|
});
|
|
1342
1508
|
}
|
|
1343
1509
|
}
|
|
1344
1510
|
}
|
|
1511
|
+
function _applyCommentMarkerValue(anchor, value, rootPath) {
|
|
1512
|
+
if (_isStructuralFragment(value)) {
|
|
1513
|
+
_mountStructuralFragmentIntoCommentRange(anchor, value, rootPath);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const end = _clearCommentPlaceholderContent(anchor);
|
|
1517
|
+
const parent = end.parentNode;
|
|
1518
|
+
if (!parent) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
const html = _renderFragmentValue(value, rootPath);
|
|
1522
|
+
if (html !== null) {
|
|
1523
|
+
parent.insertBefore(_createContextualFragment(parent, html), end);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
const textNode = (parent.ownerDocument || document).createTextNode(_coerceText(value, rootPath));
|
|
1527
|
+
parent.insertBefore(textNode, end);
|
|
1528
|
+
}
|
|
1529
|
+
function _createContextualFragment(parent, html) {
|
|
1530
|
+
const doc = parent.ownerDocument || document;
|
|
1531
|
+
if (!doc || typeof doc.createRange !== 'function') {
|
|
1532
|
+
throw new Error('[Zenith Runtime] comment placeholder HTML rendering requires Range#createContextualFragment');
|
|
1533
|
+
}
|
|
1534
|
+
const range = doc.createRange();
|
|
1535
|
+
range.selectNode(parent);
|
|
1536
|
+
return range.createContextualFragment(html);
|
|
1537
|
+
}
|
|
1345
1538
|
function _isStructuralFragment(value) {
|
|
1346
1539
|
if (Array.isArray(value)) {
|
|
1347
1540
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -1352,6 +1545,116 @@ function _isStructuralFragment(value) {
|
|
|
1352
1545
|
}
|
|
1353
1546
|
return value && typeof value === 'object' && value.__zenith_fragment === true && typeof value.mount === 'function';
|
|
1354
1547
|
}
|
|
1548
|
+
function _ensureCommentPlaceholderEnd(anchor) {
|
|
1549
|
+
let end = anchor.__z_range_end || null;
|
|
1550
|
+
if (end && end.parentNode === anchor.parentNode) {
|
|
1551
|
+
return end;
|
|
1552
|
+
}
|
|
1553
|
+
const parent = anchor.parentNode;
|
|
1554
|
+
if (!parent) {
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
end = (anchor.ownerDocument || document).createComment(`/ ${anchor.data}`);
|
|
1558
|
+
parent.insertBefore(end, anchor.nextSibling);
|
|
1559
|
+
anchor.__z_range_end = end;
|
|
1560
|
+
return end;
|
|
1561
|
+
}
|
|
1562
|
+
function _clearCommentPlaceholderContent(anchor) {
|
|
1563
|
+
if (anchor.__z_unmounts) {
|
|
1564
|
+
for (let i = 0; i < anchor.__z_unmounts.length; i++) {
|
|
1565
|
+
try {
|
|
1566
|
+
anchor.__z_unmounts[i]();
|
|
1567
|
+
}
|
|
1568
|
+
catch (e) { }
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
anchor.__z_unmounts = [];
|
|
1572
|
+
const end = _ensureCommentPlaceholderEnd(anchor);
|
|
1573
|
+
if (!end) {
|
|
1574
|
+
return anchor;
|
|
1575
|
+
}
|
|
1576
|
+
let current = anchor.nextSibling;
|
|
1577
|
+
while (current && current !== end) {
|
|
1578
|
+
const next = current.nextSibling;
|
|
1579
|
+
if (current.parentNode) {
|
|
1580
|
+
current.parentNode.removeChild(current);
|
|
1581
|
+
}
|
|
1582
|
+
current = next;
|
|
1583
|
+
}
|
|
1584
|
+
return end;
|
|
1585
|
+
}
|
|
1586
|
+
function _mountStructuralFragmentIntoCommentRange(anchor, value, rootPath = 'renderable') {
|
|
1587
|
+
const end = _clearCommentPlaceholderContent(anchor);
|
|
1588
|
+
const parent = end.parentNode;
|
|
1589
|
+
if (!parent) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
const doc = parent.ownerDocument || document;
|
|
1593
|
+
const newUnmounts = [];
|
|
1594
|
+
function insertHtml(html) {
|
|
1595
|
+
const fragment = _createContextualFragment(parent, html);
|
|
1596
|
+
const nodes = Array.from(fragment.childNodes);
|
|
1597
|
+
parent.insertBefore(fragment, end);
|
|
1598
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1599
|
+
const inserted = nodes[i];
|
|
1600
|
+
newUnmounts.push(() => {
|
|
1601
|
+
if (inserted.parentNode)
|
|
1602
|
+
inserted.parentNode.removeChild(inserted);
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
function mountItem(item, path) {
|
|
1607
|
+
if (Array.isArray(item)) {
|
|
1608
|
+
for (let i = 0; i < item.length; i++)
|
|
1609
|
+
mountItem(item[i], `${path}[${i}]`);
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
if (item && item.__zenith_fragment === true && typeof item.mount === 'function') {
|
|
1613
|
+
const fragment = doc.createDocumentFragment();
|
|
1614
|
+
item.mount(fragment);
|
|
1615
|
+
const nodes = Array.from(fragment.childNodes);
|
|
1616
|
+
parent.insertBefore(fragment, end);
|
|
1617
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1618
|
+
const inserted = nodes[i];
|
|
1619
|
+
newUnmounts.push(() => {
|
|
1620
|
+
if (inserted.parentNode)
|
|
1621
|
+
inserted.parentNode.removeChild(inserted);
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
if (typeof item.unmount === 'function') {
|
|
1625
|
+
newUnmounts.push(item.unmount.bind(item));
|
|
1626
|
+
}
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
if (item && item.__zenith_fragment === true && typeof item.html === 'string') {
|
|
1630
|
+
insertHtml(item.html);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
const text = _coerceText(item, path);
|
|
1634
|
+
if (text || text === '') {
|
|
1635
|
+
const textNode = doc.createTextNode(text);
|
|
1636
|
+
parent.insertBefore(textNode, end);
|
|
1637
|
+
newUnmounts.push(() => {
|
|
1638
|
+
if (textNode.parentNode)
|
|
1639
|
+
textNode.parentNode.removeChild(textNode);
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
mountItem(value, rootPath);
|
|
1645
|
+
}
|
|
1646
|
+
catch (error) {
|
|
1647
|
+
rethrowZenithRuntimeError(error, {
|
|
1648
|
+
phase: 'render',
|
|
1649
|
+
code: 'FRAGMENT_MOUNT_FAILED',
|
|
1650
|
+
message: 'Fragment mount failed',
|
|
1651
|
+
path: rootPath,
|
|
1652
|
+
hint: 'Verify fragment values and nested renderable arrays.',
|
|
1653
|
+
docsLink: DOCS_LINKS.markerTable
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
anchor.__z_unmounts = newUnmounts;
|
|
1657
|
+
}
|
|
1355
1658
|
function _mountStructuralFragment(container, value, rootPath = 'renderable') {
|
|
1356
1659
|
if (container.__z_unmounts) {
|
|
1357
1660
|
for (let i = 0; i < container.__z_unmounts.length; i++) {
|
|
@@ -1396,7 +1699,8 @@ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
|
|
|
1396
1699
|
code: 'FRAGMENT_MOUNT_FAILED',
|
|
1397
1700
|
message: 'Fragment mount failed',
|
|
1398
1701
|
path: rootPath,
|
|
1399
|
-
hint: 'Verify fragment values and nested renderable arrays.'
|
|
1702
|
+
hint: 'Verify fragment values and nested renderable arrays.',
|
|
1703
|
+
docsLink: DOCS_LINKS.markerTable
|
|
1400
1704
|
});
|
|
1401
1705
|
}
|
|
1402
1706
|
container.__z_unmounts = newUnmounts;
|
|
@@ -1410,7 +1714,8 @@ function _coerceText(value, path = 'renderable') {
|
|
|
1410
1714
|
code: 'NON_RENDERABLE_VALUE',
|
|
1411
1715
|
message: `Zenith Render Error: non-renderable function at ${path}. Use map() to render fields.`,
|
|
1412
1716
|
path,
|
|
1413
|
-
hint: 'Convert functions into explicit event handlers or renderable text.'
|
|
1717
|
+
hint: 'Convert functions into explicit event handlers or renderable text.',
|
|
1718
|
+
docsLink: DOCS_LINKS.expressionScope
|
|
1414
1719
|
});
|
|
1415
1720
|
}
|
|
1416
1721
|
if (value && typeof value === 'object') {
|
|
@@ -1419,7 +1724,8 @@ function _coerceText(value, path = 'renderable') {
|
|
|
1419
1724
|
code: 'NON_RENDERABLE_VALUE',
|
|
1420
1725
|
message: `Zenith Render Error: non-renderable object at ${path}. Use map() to render fields.`,
|
|
1421
1726
|
path,
|
|
1422
|
-
hint: 'Use map() to render object fields into nodes.'
|
|
1727
|
+
hint: 'Use map() to render object fields into nodes.',
|
|
1728
|
+
docsLink: DOCS_LINKS.expressionScope
|
|
1423
1729
|
});
|
|
1424
1730
|
}
|
|
1425
1731
|
return String(value);
|
|
@@ -1484,7 +1790,12 @@ function _applyAttribute(node, attrName, value) {
|
|
|
1484
1790
|
return;
|
|
1485
1791
|
}
|
|
1486
1792
|
if (attrName === 'class' || attrName === 'className') {
|
|
1487
|
-
|
|
1793
|
+
const classValue = value === null || value === undefined || value === false ? '' : String(value);
|
|
1794
|
+
if (node && node.namespaceURI === SVG_NAMESPACE && typeof node.setAttribute === 'function') {
|
|
1795
|
+
node.setAttribute('class', classValue);
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
node.className = classValue;
|
|
1488
1799
|
return;
|
|
1489
1800
|
}
|
|
1490
1801
|
if (attrName === 'style') {
|
package/dist/template.js
CHANGED
|
@@ -113,6 +113,8 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
let cssSwapEpoch = 0;
|
|
116
|
+
const CSS_UPDATE_DOCS = '/docs/documentation/contracts/hmr-v1-contract.md#css-updates';
|
|
117
|
+
const BUILD_ERROR_DOCS = '/docs/documentation/contracts/runtime-contract.md#diagnostics';
|
|
116
118
|
|
|
117
119
|
function withCacheBuster(nextHref) {
|
|
118
120
|
const separator = nextHref.includes('?') ? '&' : '?';
|
|
@@ -153,7 +155,7 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
153
155
|
|
|
154
156
|
function scheduleCssRetry(previousHref, attempt) {
|
|
155
157
|
if (attempt >= 3) {
|
|
156
|
-
|
|
158
|
+
reportBuildFailure('CSS update failed (404): server build not ready', CSS_UPDATE_DOCS);
|
|
157
159
|
return;
|
|
158
160
|
}
|
|
159
161
|
const delayMs = (attempt + 1) * 100;
|
|
@@ -220,6 +222,19 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
220
222
|
shell.style.fontSize = '12px';
|
|
221
223
|
shell.style.pointerEvents = 'none';
|
|
222
224
|
|
|
225
|
+
const buildFailure = document.createElement('div');
|
|
226
|
+
buildFailure.setAttribute('data-zenith-dev-build-error', 'true');
|
|
227
|
+
buildFailure.style.display = 'none';
|
|
228
|
+
buildFailure.style.marginBottom = '8px';
|
|
229
|
+
buildFailure.style.padding = '8px 10px';
|
|
230
|
+
buildFailure.style.maxWidth = '420px';
|
|
231
|
+
buildFailure.style.pointerEvents = 'auto';
|
|
232
|
+
buildFailure.style.border = '1px solid rgba(255,106,106,0.8)';
|
|
233
|
+
buildFailure.style.borderRadius = '8px';
|
|
234
|
+
buildFailure.style.background = 'rgba(90, 16, 16, 0.92)';
|
|
235
|
+
buildFailure.style.color = '#ffe8e8';
|
|
236
|
+
buildFailure.style.whiteSpace = 'pre-wrap';
|
|
237
|
+
|
|
223
238
|
const pill = document.createElement('button');
|
|
224
239
|
pill.type = 'button';
|
|
225
240
|
pill.textContent = 'Zenith Dev';
|
|
@@ -243,6 +258,29 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
243
258
|
panel.style.borderRadius = '10px';
|
|
244
259
|
panel.style.padding = '10px';
|
|
245
260
|
panel.style.boxShadow = '0 14px 30px rgba(0,0,0,0.35)';
|
|
261
|
+
let lastBuildFailureSignature = '';
|
|
262
|
+
|
|
263
|
+
function reportBuildFailure(message, docsLink) {
|
|
264
|
+
const normalizedMessage = typeof message === 'string' && message.trim().length > 0
|
|
265
|
+
? message.trim()
|
|
266
|
+
: 'Build failed - fix errors to continue.';
|
|
267
|
+
const signature = normalizedMessage + '|' + String(docsLink || '');
|
|
268
|
+
if (signature !== lastBuildFailureSignature) {
|
|
269
|
+
appendLog('[build_failed] ' + normalizedMessage);
|
|
270
|
+
lastBuildFailureSignature = signature;
|
|
271
|
+
}
|
|
272
|
+
const docsText = typeof docsLink === 'string' && docsLink.length > 0
|
|
273
|
+
? '\\nDocs: ' + docsLink
|
|
274
|
+
: '';
|
|
275
|
+
buildFailure.textContent = 'Build failed - fix errors to continue\\n' + normalizedMessage + docsText;
|
|
276
|
+
buildFailure.style.display = 'block';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function clearBuildFailure() {
|
|
280
|
+
lastBuildFailureSignature = '';
|
|
281
|
+
buildFailure.style.display = 'none';
|
|
282
|
+
buildFailure.textContent = '';
|
|
283
|
+
}
|
|
246
284
|
|
|
247
285
|
const status = document.createElement('div');
|
|
248
286
|
status.textContent = 'status: connecting';
|
|
@@ -292,7 +330,7 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
292
330
|
logs.textContent = '[zenith-dev] waiting for server events...';
|
|
293
331
|
|
|
294
332
|
panel.append(status, info, controls, logs);
|
|
295
|
-
shell.append(pill, panel);
|
|
333
|
+
shell.append(buildFailure, pill, panel);
|
|
296
334
|
|
|
297
335
|
function setOpen(open) {
|
|
298
336
|
state.overlay.open = open === true;
|
|
@@ -313,6 +351,9 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
313
351
|
const serverUrl = typeof payload.serverUrl === 'string' ? payload.serverUrl : window.location.origin;
|
|
314
352
|
const buildId = Number.isInteger(payload.buildId) ? payload.buildId : 'n/a';
|
|
315
353
|
const buildStatus = typeof payload.status === 'string' ? payload.status : 'unknown';
|
|
354
|
+
const errorMessage = payload && payload.error && typeof payload.error.message === 'string'
|
|
355
|
+
? payload.error.message
|
|
356
|
+
: (typeof payload.message === 'string' ? payload.message : '');
|
|
316
357
|
info.textContent =
|
|
317
358
|
'server: ' + serverUrl + '\\n' +
|
|
318
359
|
'route: ' + route + '\\n' +
|
|
@@ -321,6 +362,11 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
321
362
|
'hash: ' + hash + '\\n' +
|
|
322
363
|
'duration: ' + duration + '\\n' +
|
|
323
364
|
'changed: ' + changed;
|
|
365
|
+
if (buildStatus === 'error') {
|
|
366
|
+
reportBuildFailure(errorMessage || 'Dev build is in an error state.', BUILD_ERROR_DOCS);
|
|
367
|
+
} else if (buildStatus === 'ok' || buildStatus === 'building') {
|
|
368
|
+
clearBuildFailure();
|
|
369
|
+
}
|
|
324
370
|
}
|
|
325
371
|
|
|
326
372
|
function allLogsEnabled() {
|
|
@@ -388,12 +434,14 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
388
434
|
source.addEventListener('build_start', function (event) {
|
|
389
435
|
const payload = parseEventData(event.data);
|
|
390
436
|
status.textContent = 'status: rebuilding';
|
|
437
|
+
clearBuildFailure();
|
|
391
438
|
appendLog('[build_start] ' + (Array.isArray(payload.changedFiles) ? payload.changedFiles.join(', ') : ''));
|
|
392
439
|
emitDebug('build_start', payload);
|
|
393
440
|
});
|
|
394
441
|
source.addEventListener('build_complete', function (event) {
|
|
395
442
|
const payload = parseEventData(event.data);
|
|
396
443
|
status.textContent = 'status: ready';
|
|
444
|
+
clearBuildFailure();
|
|
397
445
|
updateInfo(payload);
|
|
398
446
|
appendLog('[build_complete] ' + (Number.isFinite(payload.durationMs) ? payload.durationMs + 'ms' : 'done'));
|
|
399
447
|
emitDebug('build_complete', payload);
|
|
@@ -401,6 +449,8 @@ const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
|
401
449
|
source.addEventListener('build_error', function (event) {
|
|
402
450
|
const payload = parseEventData(event.data);
|
|
403
451
|
status.textContent = 'status: error';
|
|
452
|
+
reportBuildFailure(payload.message || 'Build failed', BUILD_ERROR_DOCS);
|
|
453
|
+
updateInfo(payload);
|
|
404
454
|
appendLog('[build_error] ' + (payload.message || 'Unknown error'));
|
|
405
455
|
emitDebug('build_error', payload);
|
|
406
456
|
});
|