@zenithbuild/runtime 0.2.1 → 0.5.0-beta.2.12

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.
@@ -0,0 +1,1641 @@
1
+ // ---------------------------------------------------------------------------
2
+ // hydrate.js — Zenith Runtime V0
3
+ // ---------------------------------------------------------------------------
4
+ // Contract-driven hydration engine.
5
+ //
6
+ // Runtime discovery is forbidden: this module only resolves selectors from
7
+ // bundler-provided tables and executes explicit index-based bindings.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import { _registerDisposer, _registerListener, cleanup } from './cleanup.js';
11
+ import { isZenithRuntimeError, rethrowZenithRuntimeError, throwZenithRuntimeError } from './diagnostics.js';
12
+ import { signal } from './signal.js';
13
+ import { state } from './state.js';
14
+ import {
15
+ zeneffect,
16
+ zenMount,
17
+ createSideEffectScope,
18
+ activateSideEffectScope,
19
+ disposeSideEffectScope
20
+ } from './zeneffect.js';
21
+
22
+ const ALIAS_CONFLICT = Symbol('alias_conflict');
23
+ const ACTIVE_MARKER_CLASS = 'z-active';
24
+ const UNRESOLVED_LITERAL = Symbol('unresolved_literal');
25
+ const LEGACY_MARKUP_HELPER = 'html';
26
+
27
+ const BOOLEAN_ATTRIBUTES = new Set([
28
+ 'disabled', 'checked', 'selected', 'readonly', 'multiple',
29
+ 'hidden', 'autofocus', 'required', 'open'
30
+ ]);
31
+
32
+ const STRICT_MEMBER_CHAIN_LITERAL_RE = /^(?:true|false|null|undefined|[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*)$/;
33
+ const UNSAFE_MEMBER_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
34
+
35
+ const COMPILED_LITERAL_CACHE = new Map();
36
+
37
+ /**
38
+ * Hydrate a pre-rendered DOM tree using explicit payload tables.
39
+ *
40
+ * @param {{
41
+ * ir_version: number,
42
+ * root: Document | Element,
43
+ * expressions: Array<{ marker_index: number, signal_index?: number|null, state_index?: number|null, component_instance?: string|null, component_binding?: string|null, literal?: string|null }>,
44
+ * markers: Array<{ index: number, kind: 'text' | 'attr' | 'event', selector: string, attr?: string }>,
45
+ * events: Array<{ index: number, event: string, selector: string }>,
46
+ * state_values: Array<*>,
47
+ * state_keys?: Array<string>,
48
+ * signals: Array<{ id: number, kind: 'signal', state_index: number }>,
49
+ * components?: Array<{ instance: string, selector: string, create: Function }>
50
+ * }} payload
51
+ * @returns {() => void}
52
+ */
53
+ export function hydrate(payload) {
54
+ cleanup();
55
+ try {
56
+ const normalized = _validatePayload(payload);
57
+ _deepFreezePayload(payload);
58
+ const { root, expressions, markers, events, stateValues, stateKeys, signals, components, route, params, ssrData, props } = normalized;
59
+
60
+ const componentBindings = Object.create(null);
61
+
62
+ const signalMap = new Map();
63
+ for (let i = 0; i < signals.length; i++) {
64
+ const signalDescriptor = signals[i];
65
+ const candidate = stateValues[signalDescriptor.state_index];
66
+ if (!candidate || typeof candidate !== 'object') {
67
+ throw new Error(`[Zenith Runtime] signal id ${signalDescriptor.id} did not resolve to an object`);
68
+ }
69
+ if (typeof candidate.get !== 'function' || typeof candidate.subscribe !== 'function') {
70
+ throw new Error(`[Zenith Runtime] signal id ${signalDescriptor.id} must expose get() and subscribe()`);
71
+ }
72
+ signalMap.set(i, candidate);
73
+ }
74
+
75
+ for (let i = 0; i < components.length; i++) {
76
+ const component = components[i];
77
+ const resolvedProps = Object.freeze(_resolveComponentProps(component.props || [], signalMap, {
78
+ component: component.instance,
79
+ route
80
+ }));
81
+ const hosts = _resolveNodes(root, component.selector, i, 'component');
82
+ for (let j = 0; j < hosts.length; j++) {
83
+ const componentScope = createSideEffectScope(`${component.instance}:${j}`);
84
+ const runtimeApi = {
85
+ signal,
86
+ state,
87
+ zeneffect(effect, dependenciesOrOptions) {
88
+ return zeneffect(effect, dependenciesOrOptions, componentScope);
89
+ },
90
+ zenEffect(effect, options) {
91
+ return zenEffect(effect, options, componentScope);
92
+ },
93
+ zenMount(callback) {
94
+ return zenMount(callback, componentScope);
95
+ }
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
+ }
104
+ activateSideEffectScope(componentScope);
105
+ _registerDisposer(() => {
106
+ disposeSideEffectScope(componentScope);
107
+ if (typeof instance.destroy === 'function') {
108
+ instance.destroy();
109
+ }
110
+ });
111
+ if (instance.bindings && typeof instance.bindings === 'object') {
112
+ componentBindings[component.instance] = instance.bindings;
113
+ }
114
+ }
115
+ }
116
+
117
+ const expressionMarkerIndices = new Set();
118
+ for (let i = 0; i < expressions.length; i++) {
119
+ const expression = expressions[i];
120
+ if (expressionMarkerIndices.has(expression.marker_index)) {
121
+ throw new Error(`[Zenith Runtime] duplicate expression marker_index ${expression.marker_index}`);
122
+ }
123
+ expressionMarkerIndices.add(expression.marker_index);
124
+ }
125
+
126
+ const markerByIndex = new Map();
127
+ const markerNodesByIndex = new Map();
128
+ const markerIndices = new Set();
129
+ for (let i = 0; i < markers.length; i++) {
130
+ const marker = markers[i];
131
+ if (markerIndices.has(marker.index)) {
132
+ throw new Error(`[Zenith Runtime] duplicate marker index ${marker.index}`);
133
+ }
134
+ markerIndices.add(marker.index);
135
+ markerByIndex.set(marker.index, marker);
136
+
137
+ if (marker.kind === 'event') {
138
+ continue;
139
+ }
140
+
141
+ const nodes = _resolveNodes(root, marker.selector, marker.index, marker.kind);
142
+ markerNodesByIndex.set(marker.index, nodes);
143
+ const value = _evaluateExpression(
144
+ expressions[marker.index],
145
+ stateValues,
146
+ stateKeys,
147
+ signalMap,
148
+ componentBindings,
149
+ params,
150
+ ssrData,
151
+ marker.kind,
152
+ props
153
+ );
154
+ _applyMarkerValue(nodes, marker, value);
155
+ }
156
+
157
+ for (let i = 0; i < expressions.length; i++) {
158
+ if (!markerIndices.has(i)) {
159
+ throw new Error(`[Zenith Runtime] missing marker index ${i}`);
160
+ }
161
+ }
162
+
163
+ function renderMarkerByIndex(index) {
164
+ const marker = markerByIndex.get(index);
165
+ if (!marker || marker.kind === 'event') {
166
+ return;
167
+ }
168
+ const nodes = markerNodesByIndex.get(index) || _resolveNodes(root, marker.selector, marker.index, marker.kind);
169
+ markerNodesByIndex.set(index, nodes);
170
+ const value = _evaluateExpression(
171
+ expressions[index],
172
+ stateValues,
173
+ stateKeys,
174
+ signalMap,
175
+ componentBindings,
176
+ params,
177
+ ssrData,
178
+ marker.kind,
179
+ props
180
+ );
181
+ _applyMarkerValue(nodes, marker, value);
182
+ }
183
+
184
+ const dependentMarkersBySignal = new Map();
185
+ for (let i = 0; i < expressions.length; i++) {
186
+ const expression = expressions[i];
187
+ if (!Number.isInteger(expression.signal_index)) {
188
+ continue;
189
+ }
190
+ if (!dependentMarkersBySignal.has(expression.signal_index)) {
191
+ dependentMarkersBySignal.set(expression.signal_index, []);
192
+ }
193
+ dependentMarkersBySignal.get(expression.signal_index).push(expression.marker_index);
194
+ }
195
+
196
+ for (const [signalId, markerIndexes] of dependentMarkersBySignal.entries()) {
197
+ const targetSignal = signalMap.get(signalId);
198
+ if (!targetSignal) {
199
+ throw new Error(`[Zenith Runtime] expression references unknown signal id ${signalId}`);
200
+ }
201
+ const unsubscribe = targetSignal.subscribe(() => {
202
+ for (let i = 0; i < markerIndexes.length; i++) {
203
+ renderMarkerByIndex(markerIndexes[i]);
204
+ }
205
+ });
206
+ if (typeof unsubscribe === 'function') {
207
+ _registerDisposer(unsubscribe);
208
+ }
209
+ }
210
+
211
+ const dependentMarkersByComponentSignal = new Map();
212
+ for (let i = 0; i < expressions.length; i++) {
213
+ const expression = expressions[i];
214
+ if (!expression || typeof expression !== 'object') {
215
+ continue;
216
+ }
217
+ if (typeof expression.component_instance !== 'string' || typeof expression.component_binding !== 'string') {
218
+ continue;
219
+ }
220
+ const instanceBindings = componentBindings[expression.component_instance];
221
+ const candidate =
222
+ instanceBindings && Object.prototype.hasOwnProperty.call(instanceBindings, expression.component_binding)
223
+ ? instanceBindings[expression.component_binding]
224
+ : undefined;
225
+ if (!candidate || typeof candidate !== 'object') {
226
+ continue;
227
+ }
228
+ if (typeof candidate.get !== 'function' || typeof candidate.subscribe !== 'function') {
229
+ continue;
230
+ }
231
+ if (!dependentMarkersByComponentSignal.has(candidate)) {
232
+ dependentMarkersByComponentSignal.set(candidate, []);
233
+ }
234
+ dependentMarkersByComponentSignal.get(candidate).push(expression.marker_index);
235
+ }
236
+
237
+ for (const [componentSignal, markerIndexes] of dependentMarkersByComponentSignal.entries()) {
238
+ const unsubscribe = componentSignal.subscribe(() => {
239
+ for (let i = 0; i < markerIndexes.length; i++) {
240
+ renderMarkerByIndex(markerIndexes[i]);
241
+ }
242
+ });
243
+ if (typeof unsubscribe === 'function') {
244
+ _registerDisposer(unsubscribe);
245
+ }
246
+ }
247
+
248
+ const eventIndices = new Set();
249
+ for (let i = 0; i < events.length; i++) {
250
+ const eventBinding = events[i];
251
+ if (eventIndices.has(eventBinding.index)) {
252
+ throw new Error(`[Zenith Runtime] duplicate event index ${eventBinding.index}`);
253
+ }
254
+ eventIndices.add(eventBinding.index);
255
+
256
+ const nodes = _resolveNodes(root, eventBinding.selector, eventBinding.index, 'event');
257
+ const handler = _evaluateExpression(
258
+ expressions[eventBinding.index],
259
+ stateValues,
260
+ stateKeys,
261
+ signalMap,
262
+ componentBindings,
263
+ params,
264
+ ssrData,
265
+ 'event'
266
+ );
267
+ if (typeof handler !== 'function') {
268
+ throwZenithRuntimeError({
269
+ phase: 'bind',
270
+ code: 'BINDING_APPLY_FAILED',
271
+ message: `Event binding at index ${eventBinding.index} did not resolve to a function`,
272
+ marker: { type: `data-zx-on-${eventBinding.event}`, id: eventBinding.index },
273
+ path: `event[${eventBinding.index}].${eventBinding.event}`,
274
+ hint: 'Bind events to function references (on:click={handler}).'
275
+ });
276
+ }
277
+
278
+ for (let j = 0; j < nodes.length; j++) {
279
+ const node = nodes[j];
280
+ const wrappedHandler = function zenithEventHandler(event) {
281
+ try {
282
+ return handler.call(this, event);
283
+ } catch (error) {
284
+ rethrowZenithRuntimeError(error, {
285
+ phase: 'event',
286
+ code: 'EVENT_HANDLER_FAILED',
287
+ message: `Event handler failed for "${eventBinding.event}"`,
288
+ marker: { type: `data-zx-on-${eventBinding.event}`, id: eventBinding.index },
289
+ path: `event[${eventBinding.index}].${eventBinding.event}`,
290
+ hint: 'Inspect the handler body and referenced state.'
291
+ });
292
+ }
293
+ };
294
+ node.addEventListener(eventBinding.event, wrappedHandler);
295
+ _registerListener(node, eventBinding.event, wrappedHandler);
296
+ }
297
+ }
298
+
299
+ return cleanup;
300
+ } catch (error) {
301
+ rethrowZenithRuntimeError(error, {
302
+ phase: 'hydrate',
303
+ code: 'BINDING_APPLY_FAILED',
304
+ hint: 'Inspect marker tables, expression bindings, and the runtime overlay diagnostics.'
305
+ });
306
+ }
307
+ }
308
+
309
+ function _validatePayload(payload) {
310
+ if (!payload || typeof payload !== 'object') {
311
+ throw new Error('[Zenith Runtime] hydrate(payload) requires an object payload');
312
+ }
313
+
314
+ if (payload.ir_version !== 1) {
315
+ throw new Error('[Zenith Runtime] unsupported ir_version (expected 1)');
316
+ }
317
+
318
+ const root = payload.root;
319
+ const hasQuery = !!root && typeof root.querySelectorAll === 'function';
320
+ if (!hasQuery) {
321
+ throw new Error('[Zenith Runtime] hydrate(payload) requires payload.root with querySelectorAll');
322
+ }
323
+
324
+ const expressions = payload.expressions;
325
+ if (!Array.isArray(expressions)) {
326
+ throw new Error('[Zenith Runtime] hydrate(payload) requires expressions[]');
327
+ }
328
+
329
+ const markers = payload.markers;
330
+ if (!Array.isArray(markers)) {
331
+ throw new Error('[Zenith Runtime] hydrate(payload) requires markers[]');
332
+ }
333
+
334
+ const events = payload.events;
335
+ if (!Array.isArray(events)) {
336
+ throw new Error('[Zenith Runtime] hydrate(payload) requires events[]');
337
+ }
338
+
339
+ const stateValues = payload.state_values;
340
+ if (!Array.isArray(stateValues)) {
341
+ throw new Error('[Zenith Runtime] hydrate(payload) requires state_values[]');
342
+ }
343
+ const stateKeys = Array.isArray(payload.state_keys) ? payload.state_keys : [];
344
+ if (!Array.isArray(stateKeys)) {
345
+ throw new Error('[Zenith Runtime] hydrate(payload) requires state_keys[] when provided');
346
+ }
347
+ for (let i = 0; i < stateKeys.length; i++) {
348
+ if (typeof stateKeys[i] !== 'string') {
349
+ throw new Error(`[Zenith Runtime] state_keys[${i}] must be a string`);
350
+ }
351
+ }
352
+
353
+ const signals = payload.signals;
354
+ if (!Array.isArray(signals)) {
355
+ throw new Error('[Zenith Runtime] hydrate(payload) requires signals[]');
356
+ }
357
+
358
+ const components = Array.isArray(payload.components) ? payload.components : [];
359
+ const route = typeof payload.route === 'string' && payload.route.length > 0
360
+ ? payload.route
361
+ : '<unknown>';
362
+ const params = payload.params && typeof payload.params === 'object'
363
+ ? payload.params
364
+ : {};
365
+ const ssrData = payload.ssr_data && typeof payload.ssr_data === 'object'
366
+ ? payload.ssr_data
367
+ : {};
368
+
369
+ if (markers.length !== expressions.length) {
370
+ throw new Error(
371
+ `[Zenith Runtime] marker/expression mismatch: markers=${markers.length}, expressions=${expressions.length}`
372
+ );
373
+ }
374
+
375
+ for (let i = 0; i < expressions.length; i++) {
376
+ const expression = expressions[i];
377
+ if (!expression || typeof expression !== 'object' || Array.isArray(expression)) {
378
+ throw new Error(`[Zenith Runtime] expression at position ${i} must be an object`);
379
+ }
380
+ if (!Number.isInteger(expression.marker_index) || expression.marker_index < 0 || expression.marker_index >= expressions.length) {
381
+ throw new Error(`[Zenith Runtime] expression at position ${i} has invalid marker_index`);
382
+ }
383
+ if (expression.marker_index !== i) {
384
+ throw new Error(
385
+ `[Zenith Runtime] expression table out of order at position ${i}: marker_index=${expression.marker_index}`
386
+ );
387
+ }
388
+ }
389
+
390
+ for (let i = 0; i < markers.length; i++) {
391
+ const marker = markers[i];
392
+ if (!marker || typeof marker !== 'object' || Array.isArray(marker)) {
393
+ throw new Error(`[Zenith Runtime] marker at position ${i} must be an object`);
394
+ }
395
+ if (!Number.isInteger(marker.index) || marker.index < 0 || marker.index >= expressions.length) {
396
+ throw new Error(`[Zenith Runtime] marker at position ${i} has out-of-bounds index`);
397
+ }
398
+ if (marker.index !== i) {
399
+ throw new Error(`[Zenith Runtime] marker table out of order at position ${i}: index=${marker.index}`);
400
+ }
401
+ if (marker.kind !== 'text' && marker.kind !== 'attr' && marker.kind !== 'event') {
402
+ throw new Error(`[Zenith Runtime] marker at position ${i} has invalid kind`);
403
+ }
404
+ if (typeof marker.selector !== 'string' || marker.selector.length === 0) {
405
+ throw new Error(`[Zenith Runtime] marker at position ${i} requires selector`);
406
+ }
407
+ if (marker.kind === 'attr' && (typeof marker.attr !== 'string' || marker.attr.length === 0)) {
408
+ throw new Error(`[Zenith Runtime] attr marker at position ${i} requires attr name`);
409
+ }
410
+ }
411
+
412
+ for (let i = 0; i < events.length; i++) {
413
+ const eventBinding = events[i];
414
+ if (!eventBinding || typeof eventBinding !== 'object' || Array.isArray(eventBinding)) {
415
+ throw new Error(`[Zenith Runtime] event binding at position ${i} must be an object`);
416
+ }
417
+ if (!Number.isInteger(eventBinding.index) || eventBinding.index < 0 || eventBinding.index >= expressions.length) {
418
+ throw new Error(`[Zenith Runtime] event binding at position ${i} has out-of-bounds index`);
419
+ }
420
+ if (typeof eventBinding.event !== 'string' || eventBinding.event.length === 0) {
421
+ throw new Error(`[Zenith Runtime] event binding at position ${i} requires event name`);
422
+ }
423
+ if (typeof eventBinding.selector !== 'string' || eventBinding.selector.length === 0) {
424
+ throw new Error(`[Zenith Runtime] event binding at position ${i} requires selector`);
425
+ }
426
+ }
427
+
428
+ for (let i = 0; i < signals.length; i++) {
429
+ const signalDescriptor = signals[i];
430
+ if (!signalDescriptor || typeof signalDescriptor !== 'object' || Array.isArray(signalDescriptor)) {
431
+ throw new Error(`[Zenith Runtime] signal descriptor at position ${i} must be an object`);
432
+ }
433
+ if (signalDescriptor.kind !== 'signal') {
434
+ throw new Error(`[Zenith Runtime] signal descriptor at position ${i} requires kind="signal"`);
435
+ }
436
+ if (!Number.isInteger(signalDescriptor.id) || signalDescriptor.id < 0) {
437
+ throw new Error(`[Zenith Runtime] signal descriptor at position ${i} requires non-negative id`);
438
+ }
439
+ if (signalDescriptor.id !== i) {
440
+ throw new Error(`[Zenith Runtime] signal table out of order at position ${i}: id=${signalDescriptor.id}`);
441
+ }
442
+ if (!Number.isInteger(signalDescriptor.state_index) || signalDescriptor.state_index < 0 || signalDescriptor.state_index >= stateValues.length) {
443
+ throw new Error(`[Zenith Runtime] signal descriptor at position ${i} has out-of-bounds state_index`);
444
+ }
445
+ }
446
+
447
+ for (let i = 0; i < components.length; i++) {
448
+ const component = components[i];
449
+ if (!component || typeof component !== 'object' || Array.isArray(component)) {
450
+ throw new Error(`[Zenith Runtime] component at position ${i} must be an object`);
451
+ }
452
+ if (typeof component.instance !== 'string' || component.instance.length === 0) {
453
+ throw new Error(`[Zenith Runtime] component at position ${i} requires instance`);
454
+ }
455
+ if (typeof component.selector !== 'string' || component.selector.length === 0) {
456
+ throw new Error(`[Zenith Runtime] component at position ${i} requires selector`);
457
+ }
458
+ if (typeof component.create !== 'function') {
459
+ throw new Error(`[Zenith Runtime] component at position ${i} requires create() function`);
460
+ }
461
+ }
462
+
463
+ if (payload.params !== undefined) {
464
+ if (!payload.params || typeof payload.params !== 'object' || Array.isArray(payload.params)) {
465
+ throw new Error('[Zenith Runtime] hydrate() requires params object');
466
+ }
467
+ }
468
+
469
+ if (payload.ssr_data !== undefined) {
470
+ if (!payload.ssr_data || typeof payload.ssr_data !== 'object' || Array.isArray(payload.ssr_data)) {
471
+ throw new Error('[Zenith Runtime] hydrate() requires ssr_data object');
472
+ }
473
+ }
474
+
475
+ const props = payload.props && typeof payload.props === 'object' && !Array.isArray(payload.props)
476
+ ? payload.props
477
+ : {};
478
+ for (let i = 0; i < expressions.length; i++) Object.freeze(expressions[i]);
479
+ for (let i = 0; i < markers.length; i++) Object.freeze(markers[i]);
480
+ for (let i = 0; i < events.length; i++) Object.freeze(events[i]);
481
+ for (let i = 0; i < signals.length; i++) Object.freeze(signals[i]);
482
+ for (let i = 0; i < components.length; i++) {
483
+ const c = components[i];
484
+ if (Array.isArray(c.props)) {
485
+ for (let j = 0; j < c.props.length; j++) {
486
+ const propDesc = c.props[j];
487
+ if (
488
+ propDesc &&
489
+ typeof propDesc === 'object' &&
490
+ _isHydrationFreezableContainer(propDesc.value)
491
+ ) {
492
+ Object.freeze(propDesc.value);
493
+ }
494
+ Object.freeze(propDesc);
495
+ }
496
+ Object.freeze(c.props);
497
+ }
498
+ Object.freeze(c);
499
+ }
500
+
501
+ Object.freeze(expressions);
502
+ Object.freeze(markers);
503
+ Object.freeze(events);
504
+ Object.freeze(signals);
505
+ Object.freeze(components);
506
+
507
+ const validatedPayload = {
508
+ root,
509
+ expressions,
510
+ markers,
511
+ events,
512
+ stateValues,
513
+ stateKeys,
514
+ signals,
515
+ components,
516
+ route,
517
+ params: Object.freeze(params),
518
+ ssrData: Object.freeze(ssrData),
519
+ props: Object.freeze(props)
520
+ };
521
+
522
+ return Object.freeze(validatedPayload);
523
+ }
524
+
525
+ function _resolveComponentProps(propTable, signalMap, context = {}) {
526
+ if (!Array.isArray(propTable)) {
527
+ throw new Error('[Zenith Runtime] component props must be an array');
528
+ }
529
+ const resolved = Object.create(null);
530
+ for (let i = 0; i < propTable.length; i++) {
531
+ const descriptor = propTable[i];
532
+ if (!descriptor || typeof descriptor !== 'object' || Array.isArray(descriptor)) {
533
+ throw new Error(`[Zenith Runtime] component prop descriptor at index ${i} must be an object`);
534
+ }
535
+ if (typeof descriptor.name !== 'string' || descriptor.name.length === 0) {
536
+ throw new Error(`[Zenith Runtime] component prop descriptor at index ${i} requires non-empty name`);
537
+ }
538
+ if (Object.prototype.hasOwnProperty.call(resolved, descriptor.name)) {
539
+ throw new Error(`[Zenith Runtime] duplicate component prop "${descriptor.name}"`);
540
+ }
541
+ if (descriptor.type === 'static') {
542
+ if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
543
+ throw new Error(`[Zenith Runtime] component prop "${descriptor.name}" static value is missing`);
544
+ }
545
+ resolved[descriptor.name] = descriptor.value;
546
+ continue;
547
+ }
548
+ if (descriptor.type === 'signal') {
549
+ if (!Number.isInteger(descriptor.index)) {
550
+ throw new Error(`[Zenith Runtime] component prop "${descriptor.name}" signal index must be an integer`);
551
+ }
552
+ const signalValue = signalMap.get(descriptor.index);
553
+ if (!signalValue || typeof signalValue.get !== 'function') {
554
+ throw new Error(
555
+ `[Zenith Runtime]\nComponent: ${context.component || '<unknown>'}\nRoute: ${context.route || '<unknown>'}\nProp: ${descriptor.name}\nReason: signal index ${descriptor.index} did not resolve`
556
+ );
557
+ }
558
+ resolved[descriptor.name] = signalValue;
559
+ continue;
560
+ }
561
+ throw new Error(
562
+ `[Zenith Runtime] unsupported component prop type "${descriptor.type}" for "${descriptor.name}"`
563
+ );
564
+ }
565
+ return resolved;
566
+ }
567
+
568
+ function _resolveNodes(root, selector, index, kind) {
569
+ const nodes = root.querySelectorAll(selector);
570
+ if (!nodes || nodes.length === 0) {
571
+ throwZenithRuntimeError({
572
+ phase: 'bind',
573
+ code: 'MARKER_MISSING',
574
+ message: `Unresolved ${kind} marker index ${index}`,
575
+ marker: { type: kind, id: index },
576
+ path: `selector:${selector}`,
577
+ hint: 'Confirm SSR marker attributes and runtime selector tables match.'
578
+ });
579
+ }
580
+ return nodes;
581
+ }
582
+
583
+ function _evaluateExpression(binding, stateValues, stateKeys, signalMap, componentBindings, params, ssrData, mode, props) {
584
+ if (binding.signal_index !== null && binding.signal_index !== undefined) {
585
+ const signalValue = signalMap.get(binding.signal_index);
586
+ if (!signalValue || typeof signalValue.get !== 'function') {
587
+ throw new Error('[Zenith Runtime] expression.signal_index did not resolve to a signal');
588
+ }
589
+ return mode === 'event' ? signalValue : signalValue.get();
590
+ }
591
+
592
+ if (binding.state_index !== null && binding.state_index !== undefined) {
593
+ const resolved = stateValues[binding.state_index];
594
+ if (
595
+ mode !== 'event' &&
596
+ resolved &&
597
+ typeof resolved === 'object' &&
598
+ typeof resolved.get === 'function'
599
+ ) {
600
+ return resolved.get();
601
+ }
602
+ if (mode !== 'event' && typeof resolved === 'function') {
603
+ return resolved();
604
+ }
605
+ return resolved;
606
+ }
607
+
608
+ if (typeof binding.component_instance === 'string' && typeof binding.component_binding === 'string') {
609
+ const instanceBindings = componentBindings[binding.component_instance];
610
+ const resolved =
611
+ instanceBindings && Object.prototype.hasOwnProperty.call(instanceBindings, binding.component_binding)
612
+ ? instanceBindings[binding.component_binding]
613
+ : undefined;
614
+ if (
615
+ mode !== 'event' &&
616
+ resolved &&
617
+ typeof resolved === 'object' &&
618
+ typeof resolved.get === 'function'
619
+ ) {
620
+ return resolved.get();
621
+ }
622
+ if (mode !== 'event' && typeof resolved === 'function') {
623
+ return resolved();
624
+ }
625
+ return resolved;
626
+ }
627
+
628
+ if (binding.literal !== null && binding.literal !== undefined) {
629
+ if (typeof binding.literal === 'string') {
630
+ const trimmedLiteral = binding.literal.trim();
631
+ const strictMemberValue = _resolveStrictMemberChainLiteral(
632
+ trimmedLiteral,
633
+ stateValues,
634
+ stateKeys,
635
+ params,
636
+ ssrData,
637
+ mode,
638
+ props,
639
+ binding.marker_index
640
+ );
641
+ if (strictMemberValue !== UNRESOLVED_LITERAL) {
642
+ return strictMemberValue;
643
+ }
644
+ if (trimmedLiteral === 'data' || trimmedLiteral === 'ssr') {
645
+ return ssrData;
646
+ }
647
+ if (trimmedLiteral === 'params') {
648
+ return params;
649
+ }
650
+ if (trimmedLiteral === 'props') {
651
+ return props || {};
652
+ }
653
+
654
+ const evaluated = _evaluateLiteralExpression(
655
+ trimmedLiteral,
656
+ stateValues,
657
+ stateKeys,
658
+ params,
659
+ ssrData,
660
+ mode,
661
+ props
662
+ );
663
+ if (evaluated !== UNRESOLVED_LITERAL) {
664
+ return evaluated;
665
+ }
666
+ if (_isLikelyExpressionLiteral(trimmedLiteral)) {
667
+ throwZenithRuntimeError({
668
+ phase: 'bind',
669
+ code: 'UNRESOLVED_EXPRESSION',
670
+ message: `Failed to resolve expression literal: ${_truncateLiteralForError(trimmedLiteral)}`,
671
+ marker: {
672
+ type: _markerTypeForError(mode),
673
+ id: binding.marker_index
674
+ },
675
+ path: `expression[${binding.marker_index}]`,
676
+ hint: 'Ensure the expression references declared state keys or params/data bindings.'
677
+ });
678
+ }
679
+ }
680
+ return binding.literal;
681
+ }
682
+
683
+ return '';
684
+ }
685
+
686
+ function _throwUnresolvedMemberChainError(literal, markerIndex, mode, pathSuffix, hint) {
687
+ throwZenithRuntimeError({
688
+ phase: 'bind',
689
+ code: 'UNRESOLVED_EXPRESSION',
690
+ message: `Failed to resolve expression literal: ${_truncateLiteralForError(literal)}`,
691
+ marker: {
692
+ type: _markerTypeForError(mode),
693
+ id: markerIndex
694
+ },
695
+ path: `marker[${markerIndex}].${pathSuffix}`,
696
+ hint
697
+ });
698
+ }
699
+
700
+ function _resolveStrictMemberChainLiteral(
701
+ literal,
702
+ stateValues,
703
+ stateKeys,
704
+ params,
705
+ ssrData,
706
+ mode,
707
+
708
+ props,
709
+ markerIndex
710
+ ) {
711
+ if (typeof literal !== 'string' || !STRICT_MEMBER_CHAIN_LITERAL_RE.test(literal)) {
712
+ return UNRESOLVED_LITERAL;
713
+ }
714
+
715
+ if (literal === 'true') return true;
716
+ if (literal === 'false') return false;
717
+ if (literal === 'null') return null;
718
+ if (literal === 'undefined') return undefined;
719
+
720
+ const segments = literal.split('.');
721
+ const baseIdentifier = segments[0];
722
+ const scope = _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props);
723
+
724
+ if (!Object.prototype.hasOwnProperty.call(scope, baseIdentifier)) {
725
+ _throwUnresolvedMemberChainError(
726
+ literal,
727
+ markerIndex,
728
+ mode,
729
+ `expression.${baseIdentifier}`,
730
+ `Base identifier "${baseIdentifier}" is not bound. Check props/data/params and declared state keys.`
731
+ );
732
+ }
733
+
734
+ let cursor = scope[baseIdentifier];
735
+ let traversedPath = baseIdentifier;
736
+
737
+ for (let i = 1; i < segments.length; i++) {
738
+ const segment = segments[i];
739
+ if (UNSAFE_MEMBER_KEYS.has(segment)) {
740
+ throwZenithRuntimeError({
741
+ phase: 'bind',
742
+ code: 'UNSAFE_MEMBER_ACCESS',
743
+ message: `Blocked unsafe member access: ${segment} in path "${literal}"`,
744
+ path: `marker[${markerIndex}].expression.${literal}`,
745
+ hint: 'Property access to __proto__, prototype, and constructor is forbidden.'
746
+ });
747
+ }
748
+
749
+ if (cursor === null || cursor === undefined) {
750
+ _throwUnresolvedMemberChainError(
751
+ literal,
752
+ markerIndex,
753
+ mode,
754
+ `expression.${traversedPath}.${segment}`,
755
+ `Cannot read "${segment}" from ${traversedPath} because it is null or undefined.`
756
+ );
757
+ }
758
+
759
+ const cursorType = typeof cursor;
760
+ if (cursorType !== 'object' && cursorType !== 'function') {
761
+ _throwUnresolvedMemberChainError(
762
+ literal,
763
+ markerIndex,
764
+ mode,
765
+ `expression.${traversedPath}.${segment}`,
766
+ `Cannot read "${segment}" from ${traversedPath} because it resolved to a ${cursorType}.`
767
+ );
768
+ }
769
+
770
+ if (!Object.prototype.hasOwnProperty.call(cursor, segment)) {
771
+ _throwUnresolvedMemberChainError(
772
+ literal,
773
+ markerIndex,
774
+ mode,
775
+ `expression.${traversedPath}.${segment}`,
776
+ `Missing member "${segment}" on ${traversedPath}. Check your bindings.`
777
+ );
778
+ }
779
+
780
+ cursor = cursor[segment];
781
+ traversedPath = `${traversedPath}.${segment}`;
782
+ }
783
+
784
+ return cursor;
785
+ }
786
+
787
+ function _evaluateLiteralExpression(expression, stateValues, stateKeys, params, ssrData, mode, props) {
788
+ const scope = _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props);
789
+ const scopeKeys = Object.keys(scope);
790
+ const scopeValues = scopeKeys.map((key) => scope[key]);
791
+ const rewritten = _rewriteMarkupLiterals(expression);
792
+ const cacheKey = `${scopeKeys.join('|')}::${rewritten}`;
793
+
794
+ let evaluator = COMPILED_LITERAL_CACHE.get(cacheKey);
795
+ if (!evaluator) {
796
+ try {
797
+ evaluator = Function(
798
+ ...scopeKeys,
799
+ `return (${rewritten});`
800
+ );
801
+ } catch (err) {
802
+ if (isZenithRuntimeError(err)) throw err;
803
+ return UNRESOLVED_LITERAL;
804
+ }
805
+ COMPILED_LITERAL_CACHE.set(cacheKey, evaluator);
806
+ }
807
+
808
+ try {
809
+ return evaluator(...scopeValues);
810
+ } catch (err) {
811
+ if (isZenithRuntimeError(err)) throw err;
812
+ return UNRESOLVED_LITERAL;
813
+ }
814
+ }
815
+
816
+ function _buildLiteralScope(stateValues, stateKeys, params, ssrData, mode, props) {
817
+ const scope = Object.create(null);
818
+ scope.params = params;
819
+ scope.data = ssrData;
820
+ scope.ssr = ssrData;
821
+ scope.props = props || {};
822
+ scope.__ZENITH_INTERNAL_ZENHTML = _zenhtml;
823
+ scope.__zenith_fragment = function __zenith_fragment(html) {
824
+ return {
825
+ __zenith_fragment: true,
826
+ html: html === null || html === undefined || html === false ? '' : String(html)
827
+ };
828
+ };
829
+ scope[LEGACY_MARKUP_HELPER] = function legacyMarkup(strings, ...values) {
830
+ if (!Array.isArray(strings)) {
831
+ return scope.__zenith_fragment(strings);
832
+ }
833
+ let html = '';
834
+ for (let i = 0; i < strings.length; i++) {
835
+ html += strings[i];
836
+ if (i < values.length) {
837
+ html += _renderLegacyMarkupInterpolation(values[i]);
838
+ }
839
+ }
840
+ return scope.__zenith_fragment(html);
841
+ };
842
+ const aliasConflicts = new Set();
843
+
844
+ if (Array.isArray(stateKeys)) {
845
+ for (let i = 0; i < stateKeys.length; i++) {
846
+ const key = stateKeys[i];
847
+ if (typeof key !== 'string' || key.length === 0) {
848
+ continue;
849
+ }
850
+ if (Object.prototype.hasOwnProperty.call(scope, key)) {
851
+ continue;
852
+ }
853
+ const value = stateValues[i];
854
+ scope[key] = value;
855
+
856
+ const alias = _deriveStateAlias(key);
857
+ if (!alias || alias === key || aliasConflicts.has(alias)) {
858
+ continue;
859
+ }
860
+ if (Object.prototype.hasOwnProperty.call(scope, alias)) {
861
+ delete scope[alias];
862
+ aliasConflicts.add(alias);
863
+ continue;
864
+ }
865
+ scope[alias] = scope[key];
866
+ }
867
+ }
868
+
869
+ return scope;
870
+ }
871
+
872
+ function _deriveStateAlias(key) {
873
+ if (typeof key !== 'string' || key.length === 0) {
874
+ return null;
875
+ }
876
+ if (!key.startsWith('__')) {
877
+ return null;
878
+ }
879
+ if (key.startsWith('__z_frag_')) {
880
+ return null;
881
+ }
882
+ const segments = key.split('_').filter(Boolean);
883
+ for (let i = segments.length - 1; i >= 0; i--) {
884
+ const candidate = segments[i];
885
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate)) {
886
+ return candidate === key ? null : candidate;
887
+ }
888
+ }
889
+ const match = key.match(/([A-Za-z_$][A-Za-z0-9_$]*)$/);
890
+ if (!match) {
891
+ return null;
892
+ }
893
+ const alias = match[1];
894
+ return alias === key ? null : alias;
895
+ }
896
+
897
+ function _isLikelyExpressionLiteral(literal) {
898
+ if (typeof literal !== 'string') {
899
+ return false;
900
+ }
901
+ const trimmed = literal.trim();
902
+ if (trimmed.length === 0) {
903
+ return false;
904
+ }
905
+ if (trimmed === 'true' || trimmed === 'false' || trimmed === 'null' || trimmed === 'undefined') {
906
+ return false;
907
+ }
908
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) {
909
+ return true;
910
+ }
911
+ return /=>|[()[\]{}<>=?:.+\-*/%|&!]/.test(trimmed);
912
+ }
913
+
914
+ function _markerTypeForError(kind) {
915
+ if (kind === 'text') return 'data-zx-e';
916
+ if (kind === 'attr') return 'data-zx-attr';
917
+ if (kind === 'event') return 'data-zx-on';
918
+ return kind;
919
+ }
920
+
921
+ function _truncateLiteralForError(str) {
922
+ if (typeof str !== 'string') return String(str);
923
+ const sanitized = str
924
+ .replace(/[A-Za-z]:\\[^\s"'`]+/g, '<path>')
925
+ .replace(/\/Users\/[^\s"'`]+/g, '<path>')
926
+ .replace(/\/home\/[^\s"'`]+/g, '<path>')
927
+ .replace(/\/private\/[^\s"'`]+/g, '<path>')
928
+ .replace(/\/tmp\/[^\s"'`]+/g, '<path>')
929
+ .replace(/\/var\/folders\/[^\s"'`]+/g, '<path>');
930
+ return sanitized.length > 100 ? `${sanitized.substring(0, 97)}...` : sanitized;
931
+ }
932
+
933
+ // ──────────────────────────────────────────────────────────────────────────────
934
+ // _zenhtml — LEGACY internal helper for compiler-generated fragment expressions.
935
+ //
936
+ // This function is intentionally NOT exported. It is bound into the evaluator
937
+ // scope under __ZENITH_INTERNAL_ZENHTML so only compiler-emitted literals can
938
+ // reach it. Sanitization happens ONLY inside this helper; existing innerHTML
939
+ // sites used for explicit innerHTML={…} bindings are NOT affected.
940
+ //
941
+ // Scheduled for removal before 1.0 (replaced by full fragment protocol).
942
+ // ──────────────────────────────────────────────────────────────────────────────
943
+
944
+ const _ZENHTML_UNSAFE_TAG_RE = /<script[\s>]/i;
945
+ const _ZENHTML_EVENT_ATTR_RE = /\bon[a-z]+\s*=/i;
946
+ const _ZENHTML_JS_URL_RE = /javascript\s*:/i;
947
+ const _ZENHTML_PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
948
+ const _ZENHTML_SCRIPT_CLOSE_RE = /<\/script/gi;
949
+
950
+ function _zenhtml(strings, ...values) {
951
+ if (!Array.isArray(strings)) {
952
+ throwZenithRuntimeError({
953
+ phase: 'render',
954
+ code: 'NON_RENDERABLE_VALUE',
955
+ message: '__ZENITH_INTERNAL_ZENHTML must be called as a tagged template literal',
956
+ hint: 'This helper only accepts tagged template syntax.'
957
+ });
958
+ }
959
+
960
+ let result = '';
961
+ for (let i = 0; i < strings.length; i++) {
962
+ result += strings[i];
963
+ if (i < values.length) {
964
+ const val = values[i];
965
+ result += _zenhtml_coerce(val, i);
966
+ }
967
+ }
968
+
969
+ // Final output sanitization: reject dangerous patterns in assembled HTML
970
+ if (_ZENHTML_UNSAFE_TAG_RE.test(result)) {
971
+ throwZenithRuntimeError({
972
+ phase: 'render',
973
+ code: 'NON_RENDERABLE_VALUE',
974
+ message: 'Embedded markup expression contains forbidden <script> tag',
975
+ hint: 'Script tags are not allowed in embedded markup expressions.'
976
+ });
977
+ }
978
+ if (_ZENHTML_EVENT_ATTR_RE.test(result)) {
979
+ throwZenithRuntimeError({
980
+ phase: 'render',
981
+ code: 'NON_RENDERABLE_VALUE',
982
+ message: 'Embedded markup expression contains inline event handler (on*=)',
983
+ hint: 'Use on:event={handler} bindings instead of inline event attributes.'
984
+ });
985
+ }
986
+ if (_ZENHTML_JS_URL_RE.test(result)) {
987
+ throwZenithRuntimeError({
988
+ phase: 'render',
989
+ code: 'NON_RENDERABLE_VALUE',
990
+ message: 'Embedded markup expression contains javascript: URL',
991
+ hint: 'javascript: URLs are forbidden in embedded markup.'
992
+ });
993
+ }
994
+
995
+ // Escape any residual </script sequences
996
+ result = result.replace(_ZENHTML_SCRIPT_CLOSE_RE, '<\\/script');
997
+
998
+ return {
999
+ __zenith_fragment: true,
1000
+ html: result
1001
+ };
1002
+ }
1003
+
1004
+ function _zenhtml_coerce(val, interpolationIndex) {
1005
+ if (val === null || val === undefined || val === false) {
1006
+ return '';
1007
+ }
1008
+ if (val === true) {
1009
+ return '';
1010
+ }
1011
+ if (typeof val === 'string') {
1012
+ return val;
1013
+ }
1014
+ if (typeof val === 'number') {
1015
+ return String(val);
1016
+ }
1017
+ if (typeof val === 'object' && val.__zenith_fragment === true && typeof val.html === 'string') {
1018
+ return val.html;
1019
+ }
1020
+ if (Array.isArray(val)) {
1021
+ let out = '';
1022
+ for (let j = 0; j < val.length; j++) {
1023
+ out += _zenhtml_coerce(val[j], interpolationIndex);
1024
+ }
1025
+ return out;
1026
+ }
1027
+ if (typeof val === 'object') {
1028
+ // Check for prototype pollution keys
1029
+ const keys = Object.keys(val);
1030
+ for (let k = 0; k < keys.length; k++) {
1031
+ if (_ZENHTML_PROTO_KEYS.has(keys[k])) {
1032
+ throwZenithRuntimeError({
1033
+ phase: 'render',
1034
+ code: 'NON_RENDERABLE_VALUE',
1035
+ message: `Embedded markup interpolation[${interpolationIndex}] contains forbidden key "${keys[k]}"`,
1036
+ hint: 'Prototype pollution keys are forbidden in embedded markup expressions.'
1037
+ });
1038
+ }
1039
+ }
1040
+ throwZenithRuntimeError({
1041
+ phase: 'render',
1042
+ code: 'NON_RENDERABLE_VALUE',
1043
+ message: `Embedded markup interpolation[${interpolationIndex}] resolved to a non-renderable object`,
1044
+ hint: 'Only strings, numbers, booleans, null, undefined, arrays, and __zenith_fragment objects are allowed.'
1045
+ });
1046
+ }
1047
+ // functions and symbols are not renderable
1048
+ throwZenithRuntimeError({
1049
+ phase: 'render',
1050
+ code: 'NON_RENDERABLE_VALUE',
1051
+ message: `Embedded markup interpolation[${interpolationIndex}] resolved to type "${typeof val}"`,
1052
+ hint: 'Only strings, numbers, booleans, null, undefined, arrays, and __zenith_fragment objects are allowed.'
1053
+ });
1054
+ }
1055
+
1056
+ function _rewriteMarkupLiterals(expression) {
1057
+ let out = '';
1058
+ let index = 0;
1059
+ let quote = null;
1060
+ let escaped = false;
1061
+
1062
+ while (index < expression.length) {
1063
+ const ch = expression[index];
1064
+
1065
+ if (quote) {
1066
+ out += ch;
1067
+ if (escaped) {
1068
+ escaped = false;
1069
+ index += 1;
1070
+ continue;
1071
+ }
1072
+ if (ch === '\\') {
1073
+ escaped = true;
1074
+ index += 1;
1075
+ continue;
1076
+ }
1077
+ if (ch === quote) {
1078
+ quote = null;
1079
+ }
1080
+ index += 1;
1081
+ continue;
1082
+ }
1083
+
1084
+ if (ch === '\'' || ch === '"' || ch === '`') {
1085
+ quote = ch;
1086
+ out += ch;
1087
+ index += 1;
1088
+ continue;
1089
+ }
1090
+
1091
+ if (ch === '<') {
1092
+ const markup = _readMarkupLiteral(expression, index);
1093
+ if (markup) {
1094
+ out += `__zenith_fragment(${_markupLiteralToTemplate(markup.value)})`;
1095
+ index = markup.end;
1096
+ continue;
1097
+ }
1098
+ }
1099
+
1100
+ out += ch;
1101
+ index += 1;
1102
+ }
1103
+
1104
+ return out;
1105
+ }
1106
+
1107
+ function _readMarkupLiteral(source, start) {
1108
+ if (source[start] !== '<') {
1109
+ return null;
1110
+ }
1111
+
1112
+ const firstTag = _readTagToken(source, start);
1113
+ if (!firstTag || firstTag.isClosing) {
1114
+ return null;
1115
+ }
1116
+ if (firstTag.selfClosing) {
1117
+ return {
1118
+ value: source.slice(start, firstTag.end),
1119
+ end: firstTag.end
1120
+ };
1121
+ }
1122
+
1123
+ const stack = [firstTag.name];
1124
+ let cursor = firstTag.end;
1125
+
1126
+ while (cursor < source.length) {
1127
+ const nextLt = source.indexOf('<', cursor);
1128
+ if (nextLt < 0) {
1129
+ return null;
1130
+ }
1131
+ const token = _readTagToken(source, nextLt);
1132
+ if (!token) {
1133
+ cursor = nextLt + 1;
1134
+ continue;
1135
+ }
1136
+ cursor = token.end;
1137
+
1138
+ if (token.selfClosing) {
1139
+ continue;
1140
+ }
1141
+
1142
+ if (token.isClosing) {
1143
+ const expected = stack[stack.length - 1];
1144
+ if (token.name !== expected) {
1145
+ return null;
1146
+ }
1147
+ stack.pop();
1148
+ if (stack.length === 0) {
1149
+ return {
1150
+ value: source.slice(start, token.end),
1151
+ end: token.end
1152
+ };
1153
+ }
1154
+ continue;
1155
+ }
1156
+
1157
+ stack.push(token.name);
1158
+ }
1159
+
1160
+ return null;
1161
+ }
1162
+
1163
+ function _readTagToken(source, start) {
1164
+ if (source[start] !== '<') {
1165
+ return null;
1166
+ }
1167
+ let index = start + 1;
1168
+ let isClosing = false;
1169
+
1170
+ if (index < source.length && source[index] === '/') {
1171
+ isClosing = true;
1172
+ index += 1;
1173
+ }
1174
+
1175
+ if (index >= source.length || !/[A-Za-z_]/.test(source[index])) {
1176
+ return null;
1177
+ }
1178
+
1179
+ const nameStart = index;
1180
+ while (index < source.length && /[A-Za-z0-9:_-]/.test(source[index])) {
1181
+ index += 1;
1182
+ }
1183
+ if (index === nameStart) {
1184
+ return null;
1185
+ }
1186
+ const name = source.slice(nameStart, index);
1187
+
1188
+ let quote = null;
1189
+ let escaped = false;
1190
+ let braceDepth = 0;
1191
+ while (index < source.length) {
1192
+ const ch = source[index];
1193
+ if (quote) {
1194
+ if (escaped) {
1195
+ escaped = false;
1196
+ index += 1;
1197
+ continue;
1198
+ }
1199
+ if (ch === '\\') {
1200
+ escaped = true;
1201
+ index += 1;
1202
+ continue;
1203
+ }
1204
+ if (ch === quote) {
1205
+ quote = null;
1206
+ }
1207
+ index += 1;
1208
+ continue;
1209
+ }
1210
+
1211
+ if (ch === '\'' || ch === '"' || ch === '`') {
1212
+ quote = ch;
1213
+ index += 1;
1214
+ continue;
1215
+ }
1216
+ if (ch === '{') {
1217
+ braceDepth += 1;
1218
+ index += 1;
1219
+ continue;
1220
+ }
1221
+ if (ch === '}') {
1222
+ braceDepth = Math.max(0, braceDepth - 1);
1223
+ index += 1;
1224
+ continue;
1225
+ }
1226
+ if (ch === '>' && braceDepth === 0) {
1227
+ const segment = source.slice(start, index + 1);
1228
+ const selfClosing = !isClosing && /\/\s*>$/.test(segment);
1229
+ return { name, isClosing, selfClosing, end: index + 1 };
1230
+ }
1231
+ index += 1;
1232
+ }
1233
+
1234
+ return null;
1235
+ }
1236
+
1237
+ function _markupLiteralToTemplate(markup) {
1238
+ let out = '`';
1239
+ let index = 0;
1240
+ while (index < markup.length) {
1241
+ const ch = markup[index];
1242
+ if (ch === '{') {
1243
+ const segment = _readBalancedBraces(markup, index);
1244
+ if (segment) {
1245
+ if (_isAttributeExpressionStart(markup, index)) {
1246
+ out += '"${' + segment.content + '}"';
1247
+ } else {
1248
+ out += '${' + segment.content + '}';
1249
+ }
1250
+ index = segment.end;
1251
+ continue;
1252
+ }
1253
+ }
1254
+ if (ch === '`') {
1255
+ out += '\\`';
1256
+ index += 1;
1257
+ continue;
1258
+ }
1259
+ if (ch === '\\') {
1260
+ out += '\\\\';
1261
+ index += 1;
1262
+ continue;
1263
+ }
1264
+ if (ch === '$' && markup[index + 1] === '{') {
1265
+ out += '\\${';
1266
+ index += 2;
1267
+ continue;
1268
+ }
1269
+ out += ch;
1270
+ index += 1;
1271
+ }
1272
+ out += '`';
1273
+ return out;
1274
+ }
1275
+
1276
+ function _isAttributeExpressionStart(markup, start) {
1277
+ let cursor = start - 1;
1278
+ while (cursor >= 0) {
1279
+ const ch = markup[cursor];
1280
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
1281
+ cursor -= 1;
1282
+ continue;
1283
+ }
1284
+ return ch === '=';
1285
+ }
1286
+ return false;
1287
+ }
1288
+
1289
+ function _readBalancedBraces(source, start) {
1290
+ if (source[start] !== '{') {
1291
+ return null;
1292
+ }
1293
+ let depth = 1;
1294
+ let index = start + 1;
1295
+ let quote = null;
1296
+ let escaped = false;
1297
+ let content = '';
1298
+
1299
+ while (index < source.length) {
1300
+ const ch = source[index];
1301
+ if (quote) {
1302
+ content += ch;
1303
+ if (escaped) {
1304
+ escaped = false;
1305
+ index += 1;
1306
+ continue;
1307
+ }
1308
+ if (ch === '\\') {
1309
+ escaped = true;
1310
+ index += 1;
1311
+ continue;
1312
+ }
1313
+ if (ch === quote) {
1314
+ quote = null;
1315
+ }
1316
+ index += 1;
1317
+ continue;
1318
+ }
1319
+
1320
+ if (ch === '\'' || ch === '"' || ch === '`') {
1321
+ quote = ch;
1322
+ content += ch;
1323
+ index += 1;
1324
+ continue;
1325
+ }
1326
+ if (ch === '{') {
1327
+ depth += 1;
1328
+ content += ch;
1329
+ index += 1;
1330
+ continue;
1331
+ }
1332
+ if (ch === '}') {
1333
+ depth -= 1;
1334
+ if (depth === 0) {
1335
+ return {
1336
+ content,
1337
+ end: index + 1
1338
+ };
1339
+ }
1340
+ content += ch;
1341
+ index += 1;
1342
+ continue;
1343
+ }
1344
+ content += ch;
1345
+ index += 1;
1346
+ }
1347
+
1348
+ return null;
1349
+ }
1350
+
1351
+ function _applyMarkerValue(nodes, marker, value) {
1352
+ const markerPath = `marker[${marker.index}]`;
1353
+ for (let i = 0; i < nodes.length; i++) {
1354
+ try {
1355
+ const node = nodes[i];
1356
+ if (marker.kind === 'text') {
1357
+ if (_isStructuralFragment(value)) {
1358
+ _mountStructuralFragment(node, value, `${markerPath}.text`);
1359
+ continue;
1360
+ }
1361
+
1362
+ const html = _renderFragmentValue(value, `${markerPath}.text`);
1363
+ if (html !== null) {
1364
+ node.innerHTML = html;
1365
+ } else {
1366
+ node.textContent = _coerceText(value, `${markerPath}.text`);
1367
+ }
1368
+ continue;
1369
+ }
1370
+
1371
+ if (marker.kind === 'attr') {
1372
+ _applyAttribute(node, marker.attr, value);
1373
+ }
1374
+ } catch (error) {
1375
+ rethrowZenithRuntimeError(error, {
1376
+ phase: 'bind',
1377
+ code: 'BINDING_APPLY_FAILED',
1378
+ message: `Failed to apply ${marker.kind} binding at marker ${marker.index}`,
1379
+ marker: {
1380
+ type: marker.kind === 'attr' ? `attr:${marker.attr}` : marker.kind,
1381
+ id: marker.index
1382
+ },
1383
+ path: marker.kind === 'attr'
1384
+ ? `${markerPath}.attr.${marker.attr}`
1385
+ : `${markerPath}.${marker.kind}`,
1386
+ hint: 'Check the binding value type and marker mapping.'
1387
+ });
1388
+ }
1389
+ }
1390
+ }
1391
+
1392
+ function _isStructuralFragment(value) {
1393
+ if (Array.isArray(value)) {
1394
+ for (let i = 0; i < value.length; i++) {
1395
+ if (_isStructuralFragment(value[i])) return true;
1396
+ }
1397
+ return false;
1398
+ }
1399
+ return value && typeof value === 'object' && value.__zenith_fragment === true && typeof value.mount === 'function';
1400
+ }
1401
+
1402
+ function _mountStructuralFragment(container, value, rootPath = 'renderable') {
1403
+ if (container.__z_unmounts) {
1404
+ for (let i = 0; i < container.__z_unmounts.length; i++) {
1405
+ try { container.__z_unmounts[i](); } catch (e) { }
1406
+ }
1407
+ }
1408
+
1409
+ container.innerHTML = '';
1410
+ const newUnmounts = [];
1411
+
1412
+ function mountItem(item, path) {
1413
+ if (Array.isArray(item)) {
1414
+ for (let i = 0; i < item.length; i++) mountItem(item[i], `${path}[${i}]`);
1415
+ return;
1416
+ }
1417
+ if (item && item.__zenith_fragment === true && typeof item.mount === 'function') {
1418
+ item.mount(container);
1419
+ if (typeof item.unmount === 'function') {
1420
+ newUnmounts.push(item.unmount.bind(item));
1421
+ }
1422
+ } else {
1423
+ const text = _coerceText(item, path);
1424
+ if (text || text === '') {
1425
+ const textNode = document.createTextNode(text);
1426
+ container.appendChild(textNode);
1427
+ newUnmounts.push(() => {
1428
+ if (textNode.parentNode) textNode.parentNode.removeChild(textNode);
1429
+ });
1430
+ }
1431
+ }
1432
+ }
1433
+
1434
+ try {
1435
+ mountItem(value, rootPath);
1436
+ } catch (error) {
1437
+ rethrowZenithRuntimeError(error, {
1438
+ phase: 'render',
1439
+ code: 'FRAGMENT_MOUNT_FAILED',
1440
+ message: 'Fragment mount failed',
1441
+ path: rootPath,
1442
+ hint: 'Verify fragment values and nested renderable arrays.'
1443
+ });
1444
+ }
1445
+ container.__z_unmounts = newUnmounts;
1446
+ }
1447
+
1448
+ function _coerceText(value, path = 'renderable') {
1449
+ if (value === null || value === undefined || value === false || value === true) return '';
1450
+ if (typeof value === 'function') {
1451
+ throwZenithRuntimeError({
1452
+ phase: 'render',
1453
+ code: 'NON_RENDERABLE_VALUE',
1454
+ message: `Zenith Render Error: non-renderable function at ${path}. Use map() to render fields.`,
1455
+ path,
1456
+ hint: 'Convert functions into explicit event handlers or renderable text.'
1457
+ });
1458
+ }
1459
+ if (value && typeof value === 'object') {
1460
+ throwZenithRuntimeError({
1461
+ phase: 'render',
1462
+ code: 'NON_RENDERABLE_VALUE',
1463
+ message: `Zenith Render Error: non-renderable object at ${path}. Use map() to render fields.`,
1464
+ path,
1465
+ hint: 'Use map() to render object fields into nodes.'
1466
+ });
1467
+ }
1468
+ return String(value);
1469
+ }
1470
+
1471
+ function _renderFragmentValue(value, path = 'renderable') {
1472
+ if (value === null || value === undefined || value === false || value === true) {
1473
+ return '';
1474
+ }
1475
+ if (Array.isArray(value)) {
1476
+ let out = '';
1477
+ for (let i = 0; i < value.length; i++) {
1478
+ const itemPath = `${path}[${i}]`;
1479
+ const piece = _renderFragmentValue(value[i], itemPath);
1480
+ if (piece !== null) {
1481
+ out += piece;
1482
+ continue;
1483
+ }
1484
+ out += _escapeHtml(_coerceText(value[i], itemPath));
1485
+ }
1486
+ return out;
1487
+ }
1488
+ if (
1489
+ value &&
1490
+ typeof value === 'object' &&
1491
+ value.__zenith_fragment === true &&
1492
+ typeof value.html === 'string'
1493
+ ) {
1494
+ return value.html;
1495
+ }
1496
+ return null;
1497
+ }
1498
+
1499
+ function _escapeHtml(input) {
1500
+ return String(input)
1501
+ .replace(/&/g, '&amp;')
1502
+ .replace(/</g, '&lt;')
1503
+ .replace(/>/g, '&gt;')
1504
+ .replace(/"/g, '&quot;')
1505
+ .replace(/'/g, '&#39;');
1506
+ }
1507
+
1508
+ function _renderLegacyMarkupInterpolation(value) {
1509
+ if (value === null || value === undefined || value === false || value === true) {
1510
+ return '';
1511
+ }
1512
+ if (Array.isArray(value)) {
1513
+ let out = '';
1514
+ for (let i = 0; i < value.length; i++) {
1515
+ out += _renderLegacyMarkupInterpolation(value[i]);
1516
+ }
1517
+ return out;
1518
+ }
1519
+ if (
1520
+ value &&
1521
+ typeof value === 'object' &&
1522
+ value.__zenith_fragment === true &&
1523
+ typeof value.html === 'string'
1524
+ ) {
1525
+ return value.html;
1526
+ }
1527
+ return _escapeHtml(_coerceText(value, 'legacy markup interpolation'));
1528
+ }
1529
+
1530
+ function _applyAttribute(node, attrName, value) {
1531
+ if (typeof attrName === 'string' && attrName.toLowerCase() === 'innerhtml') {
1532
+ node.innerHTML = value === null || value === undefined || value === false
1533
+ ? ''
1534
+ : String(value);
1535
+ return;
1536
+ }
1537
+
1538
+ if (attrName === 'class' || attrName === 'className') {
1539
+ node.className = value === null || value === undefined || value === false ? '' : String(value);
1540
+ return;
1541
+ }
1542
+
1543
+ if (attrName === 'style') {
1544
+ if (value === null || value === undefined || value === false) {
1545
+ node.removeAttribute('style');
1546
+ return;
1547
+ }
1548
+
1549
+ if (typeof value === 'string') {
1550
+ node.setAttribute('style', value);
1551
+ return;
1552
+ }
1553
+
1554
+ if (typeof value === 'object') {
1555
+ const entries = Object.entries(value);
1556
+ let styleText = '';
1557
+ for (let i = 0; i < entries.length; i++) {
1558
+ const [key, rawValue] = entries[i];
1559
+ styleText += `${key}: ${rawValue};`;
1560
+ }
1561
+ node.setAttribute('style', styleText);
1562
+ return;
1563
+ }
1564
+
1565
+ node.setAttribute('style', String(value));
1566
+ return;
1567
+ }
1568
+
1569
+ if (BOOLEAN_ATTRIBUTES.has(attrName)) {
1570
+ if (value) {
1571
+ node.setAttribute(attrName, '');
1572
+ } else {
1573
+ node.removeAttribute(attrName);
1574
+ }
1575
+ return;
1576
+ }
1577
+
1578
+ if (value === null || value === undefined || value === false) {
1579
+ node.removeAttribute(attrName);
1580
+ return;
1581
+ }
1582
+
1583
+ node.setAttribute(attrName, String(value));
1584
+ }
1585
+
1586
+ function _deepFreezePayload(obj) {
1587
+ if (!_isHydrationFreezableContainer(obj) || Object.isFrozen(obj)) return;
1588
+
1589
+ Object.freeze(obj);
1590
+ const keys = Object.keys(obj);
1591
+ for (let i = 0; i < keys.length; i++) {
1592
+ const val = obj[keys[i]];
1593
+ if (val && typeof val === 'object' && typeof val !== 'function') {
1594
+ _deepFreezePayload(val);
1595
+ }
1596
+ }
1597
+ }
1598
+
1599
+ function _isHydrationRefObject(obj) {
1600
+ if (!obj || typeof obj !== 'object') {
1601
+ return false;
1602
+ }
1603
+ if (obj.__zenith_ref === true) {
1604
+ return true;
1605
+ }
1606
+ if (!Object.prototype.hasOwnProperty.call(obj, 'current')) {
1607
+ return false;
1608
+ }
1609
+ if (typeof obj.get === 'function' && typeof obj.subscribe === 'function') {
1610
+ return false;
1611
+ }
1612
+ const keys = Object.keys(obj);
1613
+ if (keys.length === 1 && keys[0] === 'current') {
1614
+ return true;
1615
+ }
1616
+ if (keys.length === 2 && keys.includes('current') && keys.includes('__zenith_ref')) {
1617
+ return true;
1618
+ }
1619
+ return false;
1620
+ }
1621
+
1622
+ function _isPlainObject(value) {
1623
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1624
+ return false;
1625
+ }
1626
+ const proto = Object.getPrototypeOf(value);
1627
+ return proto === Object.prototype || proto === null;
1628
+ }
1629
+
1630
+ function _isHydrationFreezableContainer(value) {
1631
+ if (Array.isArray(value)) return true;
1632
+ if (!_isPlainObject(value)) return false;
1633
+
1634
+ if (_isHydrationRefObject(value)) {
1635
+ return false;
1636
+ }
1637
+ if (typeof value.get === 'function' && typeof value.subscribe === 'function') {
1638
+ return false;
1639
+ }
1640
+ return true;
1641
+ }