@zenithbuild/compiler 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/build-analyzer.d.ts +44 -0
  4. package/dist/build-analyzer.js +87 -0
  5. package/dist/bundler.d.ts +31 -0
  6. package/dist/bundler.js +86 -0
  7. package/dist/core/components/index.d.ts +9 -0
  8. package/dist/core/components/index.js +13 -0
  9. package/dist/core/config/index.d.ts +11 -0
  10. package/dist/core/config/index.js +10 -0
  11. package/dist/core/config/loader.d.ts +17 -0
  12. package/dist/core/config/loader.js +60 -0
  13. package/dist/core/config/types.d.ts +98 -0
  14. package/dist/core/config/types.js +32 -0
  15. package/dist/core/index.d.ts +7 -0
  16. package/dist/core/index.js +6 -0
  17. package/dist/core/lifecycle/index.d.ts +16 -0
  18. package/dist/core/lifecycle/index.js +19 -0
  19. package/dist/core/lifecycle/zen-mount.d.ts +66 -0
  20. package/dist/core/lifecycle/zen-mount.js +151 -0
  21. package/dist/core/lifecycle/zen-unmount.d.ts +54 -0
  22. package/dist/core/lifecycle/zen-unmount.js +76 -0
  23. package/dist/core/plugins/bridge.d.ts +116 -0
  24. package/dist/core/plugins/bridge.js +121 -0
  25. package/dist/core/plugins/index.d.ts +6 -0
  26. package/dist/core/plugins/index.js +6 -0
  27. package/dist/core/plugins/registry.d.ts +67 -0
  28. package/dist/core/plugins/registry.js +113 -0
  29. package/dist/core/reactivity/index.d.ts +30 -0
  30. package/dist/core/reactivity/index.js +33 -0
  31. package/dist/core/reactivity/tracking.d.ts +74 -0
  32. package/dist/core/reactivity/tracking.js +136 -0
  33. package/dist/core/reactivity/zen-batch.d.ts +45 -0
  34. package/dist/core/reactivity/zen-batch.js +54 -0
  35. package/dist/core/reactivity/zen-effect.d.ts +48 -0
  36. package/dist/core/reactivity/zen-effect.js +98 -0
  37. package/dist/core/reactivity/zen-memo.d.ts +43 -0
  38. package/dist/core/reactivity/zen-memo.js +100 -0
  39. package/dist/core/reactivity/zen-ref.d.ts +44 -0
  40. package/dist/core/reactivity/zen-ref.js +34 -0
  41. package/dist/core/reactivity/zen-signal.d.ts +48 -0
  42. package/dist/core/reactivity/zen-signal.js +84 -0
  43. package/dist/core/reactivity/zen-state.d.ts +35 -0
  44. package/dist/core/reactivity/zen-state.js +147 -0
  45. package/dist/core/reactivity/zen-untrack.d.ts +38 -0
  46. package/dist/core/reactivity/zen-untrack.js +41 -0
  47. package/dist/css/index.d.ts +73 -0
  48. package/dist/css/index.js +246 -0
  49. package/dist/discovery/componentDiscovery.d.ts +42 -0
  50. package/dist/discovery/componentDiscovery.js +56 -0
  51. package/dist/discovery/layouts.d.ts +13 -0
  52. package/dist/discovery/layouts.js +41 -0
  53. package/dist/errors/compilerError.d.ts +31 -0
  54. package/dist/errors/compilerError.js +51 -0
  55. package/dist/finalize/finalizeOutput.d.ts +32 -0
  56. package/dist/finalize/finalizeOutput.js +62 -0
  57. package/dist/finalize/generateFinalBundle.d.ts +24 -0
  58. package/dist/finalize/generateFinalBundle.js +68 -0
  59. package/dist/index.d.ts +36 -0
  60. package/dist/index.js +51 -0
  61. package/dist/ir/types.d.ts +181 -0
  62. package/dist/ir/types.js +8 -0
  63. package/dist/output/types.d.ts +30 -0
  64. package/dist/output/types.js +6 -0
  65. package/dist/parse/detectMapExpressions.d.ts +45 -0
  66. package/dist/parse/detectMapExpressions.js +77 -0
  67. package/dist/parse/parseScript.d.ts +8 -0
  68. package/dist/parse/parseScript.js +36 -0
  69. package/dist/parse/parseTemplate.d.ts +11 -0
  70. package/dist/parse/parseTemplate.js +487 -0
  71. package/dist/parse/parseZenFile.d.ts +11 -0
  72. package/dist/parse/parseZenFile.js +50 -0
  73. package/dist/parse/scriptAnalysis.d.ts +25 -0
  74. package/dist/parse/scriptAnalysis.js +60 -0
  75. package/dist/parse/trackLoopContext.d.ts +20 -0
  76. package/dist/parse/trackLoopContext.js +62 -0
  77. package/dist/parseZenFile.d.ts +10 -0
  78. package/dist/parseZenFile.js +55 -0
  79. package/dist/runtime/analyzeAndEmit.d.ts +20 -0
  80. package/dist/runtime/analyzeAndEmit.js +70 -0
  81. package/dist/runtime/build.d.ts +6 -0
  82. package/dist/runtime/build.js +13 -0
  83. package/dist/runtime/bundle-generator.d.ts +27 -0
  84. package/dist/runtime/bundle-generator.js +1263 -0
  85. package/dist/runtime/client-runtime.d.ts +41 -0
  86. package/dist/runtime/client-runtime.js +397 -0
  87. package/dist/runtime/dataExposure.d.ts +52 -0
  88. package/dist/runtime/dataExposure.js +227 -0
  89. package/dist/runtime/generateDOM.d.ts +21 -0
  90. package/dist/runtime/generateDOM.js +194 -0
  91. package/dist/runtime/generateHydrationBundle.d.ts +15 -0
  92. package/dist/runtime/generateHydrationBundle.js +399 -0
  93. package/dist/runtime/hydration.d.ts +53 -0
  94. package/dist/runtime/hydration.js +271 -0
  95. package/dist/runtime/navigation.d.ts +58 -0
  96. package/dist/runtime/navigation.js +372 -0
  97. package/dist/runtime/serve.d.ts +13 -0
  98. package/dist/runtime/serve.js +76 -0
  99. package/dist/runtime/thinRuntime.d.ts +23 -0
  100. package/dist/runtime/thinRuntime.js +158 -0
  101. package/dist/runtime/transformIR.d.ts +19 -0
  102. package/dist/runtime/transformIR.js +285 -0
  103. package/dist/runtime/wrapExpression.d.ts +24 -0
  104. package/dist/runtime/wrapExpression.js +76 -0
  105. package/dist/runtime/wrapExpressionWithLoop.d.ts +17 -0
  106. package/dist/runtime/wrapExpressionWithLoop.js +75 -0
  107. package/dist/spa-build.d.ts +26 -0
  108. package/dist/spa-build.js +866 -0
  109. package/dist/ssg-build.d.ts +32 -0
  110. package/dist/ssg-build.js +408 -0
  111. package/dist/test/analyze-emit.test.d.ts +1 -0
  112. package/dist/test/analyze-emit.test.js +88 -0
  113. package/dist/test/bundler-contract.test.d.ts +1 -0
  114. package/dist/test/bundler-contract.test.js +137 -0
  115. package/dist/test/compiler-authority.test.d.ts +1 -0
  116. package/dist/test/compiler-authority.test.js +90 -0
  117. package/dist/test/component-instance-test.d.ts +1 -0
  118. package/dist/test/component-instance-test.js +115 -0
  119. package/dist/test/error-native-bridge.test.d.ts +1 -0
  120. package/dist/test/error-native-bridge.test.js +51 -0
  121. package/dist/test/error-serialization.test.d.ts +1 -0
  122. package/dist/test/error-serialization.test.js +38 -0
  123. package/dist/test/macro-inlining.test.d.ts +1 -0
  124. package/dist/test/macro-inlining.test.js +178 -0
  125. package/dist/test/validate-test.d.ts +6 -0
  126. package/dist/test/validate-test.js +95 -0
  127. package/dist/transform/classifyExpression.d.ts +46 -0
  128. package/dist/transform/classifyExpression.js +354 -0
  129. package/dist/transform/componentResolver.d.ts +15 -0
  130. package/dist/transform/componentResolver.js +30 -0
  131. package/dist/transform/expressionTransformer.d.ts +19 -0
  132. package/dist/transform/expressionTransformer.js +333 -0
  133. package/dist/transform/fragmentLowering.d.ts +25 -0
  134. package/dist/transform/fragmentLowering.js +468 -0
  135. package/dist/transform/layoutProcessor.d.ts +5 -0
  136. package/dist/transform/layoutProcessor.js +34 -0
  137. package/dist/transform/transformTemplate.d.ts +11 -0
  138. package/dist/transform/transformTemplate.js +33 -0
  139. package/dist/validate/invariants.d.ts +23 -0
  140. package/dist/validate/invariants.js +55 -0
  141. package/native/compiler-native/compiler-native.node +0 -0
  142. package/native/compiler-native/index.d.ts +113 -0
  143. package/native/compiler-native/index.js +19 -0
  144. package/native/compiler-native/package.json +19 -0
  145. package/package.json +49 -0
@@ -0,0 +1,1263 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import path from 'path';
3
+ /**
4
+ * Zenith Bundle Generator
5
+ *
6
+ * Generates the shared client runtime bundle that gets served as:
7
+ * - /assets/bundle.js in production
8
+ * - /runtime.js in development
9
+ *
10
+ * This is a cacheable, versioned file that contains:
11
+ * - Reactivity primitives (zenSignal, zenState, zenEffect, etc.)
12
+ * - Lifecycle hooks (zenOnMount, zenOnUnmount)
13
+ * - Hydration functions (zenithHydrate)
14
+ * - Event binding utilities
15
+ */
16
+ /**
17
+ * Generate the complete client runtime bundle
18
+ * This is served as an external JS file, not inlined
19
+ */
20
+ export function generateBundleJS(pluginData) {
21
+ // Serialize plugin data blindly - CLI never inspects what's inside.
22
+ // We escape </script> sequences just in case this bundle is ever inlined (unlikely but safe).
23
+ const serializedData = pluginData
24
+ ? JSON.stringify(pluginData).replace(/<\/script/g, '<\\/script')
25
+ : '{}';
26
+ // Resolve core runtime paths - assumes sibling directory or relative in node_modules
27
+ const rootDir = process.cwd();
28
+ const coreDistPath = path.join(rootDir, '../zenith-core/dist/runtime');
29
+ let reactivityJS = '';
30
+ let lifecycleJS = '';
31
+ try {
32
+ const reactivityFile = path.join(coreDistPath, 'reactivity/index.js');
33
+ const lifecycleFile = path.join(coreDistPath, 'lifecycle/index.js');
34
+ if (existsSync(reactivityFile))
35
+ reactivityJS = readFileSync(reactivityFile, 'utf-8');
36
+ if (existsSync(lifecycleFile))
37
+ lifecycleJS = readFileSync(lifecycleFile, 'utf-8');
38
+ }
39
+ catch (e) {
40
+ console.warn('[Zenith] Could not load runtime from core dist, falling back to basic support', e);
41
+ }
42
+ return `/*!
43
+ * Zenith Runtime v1.0.1
44
+ * Shared client-side runtime for hydration and reactivity
45
+ */
46
+ (function(global) {
47
+ 'use strict';
48
+
49
+ // Initialize plugin data envelope
50
+ global.__ZENITH_PLUGIN_DATA__ = ${serializedData};
51
+
52
+ ${reactivityJS ? ` // ============================================
53
+ // Core Reactivity (Injected from @zenithbuild/core)
54
+ // ============================================
55
+ ${reactivityJS}` : ` // Fallback: Reactivity not found`}
56
+
57
+ ${lifecycleJS ? ` // ============================================
58
+ // Lifecycle Hooks (Injected from @zenithbuild/core)
59
+ // ============================================
60
+ ${lifecycleJS}` : ` // Fallback: Lifecycle not found`}
61
+
62
+ // ============================================
63
+ // Component Instance System
64
+ // ============================================
65
+ // Each component instance gets isolated state, effects, and lifecycles
66
+ // Instances are tied to DOM elements via hydration markers
67
+
68
+ const componentRegistry = {};
69
+
70
+ function createComponentInstance(componentName, rootElement) {
71
+ const instanceMountCallbacks = [];
72
+ const instanceUnmountCallbacks = [];
73
+ const instanceEffects = [];
74
+ let instanceMounted = false;
75
+
76
+ return {
77
+ // DOM reference
78
+ root: rootElement,
79
+
80
+ // Lifecycle hooks (instance-scoped)
81
+ onMount: function(fn) {
82
+ if (instanceMounted) {
83
+ const cleanup = fn();
84
+ if (typeof cleanup === 'function') {
85
+ instanceUnmountCallbacks.push(cleanup);
86
+ }
87
+ } else {
88
+ instanceMountCallbacks.push(fn);
89
+ }
90
+ },
91
+ onUnmount: function(fn) {
92
+ instanceUnmountCallbacks.push(fn);
93
+ },
94
+
95
+ // Reactivity (uses global primitives but tracks for cleanup)
96
+ signal: function(initial) {
97
+ return zenSignal(initial);
98
+ },
99
+ state: function(initial) {
100
+ return zenState(initial);
101
+ },
102
+ ref: function(initial) {
103
+ return zenRef(initial);
104
+ },
105
+ effect: function(fn) {
106
+ const cleanup = zenEffect(fn);
107
+ instanceEffects.push(cleanup);
108
+ return cleanup;
109
+ },
110
+ memo: function(fn) {
111
+ return zenMemo(fn);
112
+ },
113
+ batch: function(fn) {
114
+ zenBatch(fn);
115
+ },
116
+ untrack: function(fn) {
117
+ return zenUntrack(fn);
118
+ },
119
+
120
+ // Lifecycle execution
121
+ mount: function() {
122
+ instanceMounted = true;
123
+ for (let i = 0; i < instanceMountCallbacks.length; i++) {
124
+ try {
125
+ const cleanup = instanceMountCallbacks[i]();
126
+ if (typeof cleanup === 'function') {
127
+ instanceUnmountCallbacks.push(cleanup);
128
+ }
129
+ } catch(e) {
130
+ console.error('[Zenith] Component mount error:', componentName, e);
131
+ }
132
+ }
133
+ instanceMountCallbacks.length = 0;
134
+ },
135
+ unmount: function() {
136
+ instanceMounted = false;
137
+ // Cleanup effects
138
+ for (let i = 0; i < instanceEffects.length; i++) {
139
+ try {
140
+ if (typeof instanceEffects[i] === 'function') instanceEffects[i]();
141
+ } catch(e) {
142
+ console.error('[Zenith] Effect cleanup error:', e);
143
+ }
144
+ }
145
+ instanceEffects.length = 0;
146
+ // Run unmount callbacks
147
+ for (let i = 0; i < instanceUnmountCallbacks.length; i++) {
148
+ try { instanceUnmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
149
+ }
150
+ instanceUnmountCallbacks.length = 0;
151
+ }
152
+ };
153
+ }
154
+
155
+ function defineComponent(name, factory) {
156
+ componentRegistry[name] = factory;
157
+ }
158
+
159
+ function instantiateComponent(name, props, rootElement) {
160
+ const factory = componentRegistry[name];
161
+ if (!factory) {
162
+ if (name === 'ErrorPage') {
163
+ // Built-in fallback for ErrorPage if not registered by user
164
+ return fallbackErrorPage(props, rootElement);
165
+ }
166
+ console.warn('[Zenith] Component not found:', name);
167
+ return null;
168
+ }
169
+ return factory(props, rootElement);
170
+ }
171
+
172
+ function renderErrorPage(error, metadata) {
173
+ console.error('[Zenith Runtime Error]', error, metadata);
174
+
175
+ // In production, we might want a simpler page, but for now let's use the high-fidelity one
176
+ // if it's available.
177
+ const container = document.getElementById('app') || document.body;
178
+
179
+ // If we've already rendered an error page, don't do it again to avoid infinite loops
180
+ if (window.__ZENITH_ERROR_RENDERED__) return;
181
+ window.__ZENITH_ERROR_RENDERED__ = true;
182
+
183
+ const errorProps = {
184
+ message: error.message || 'Unknown Error',
185
+ stack: error.stack,
186
+ file: metadata.file || (error.file),
187
+ line: metadata.line || (error.line),
188
+ column: metadata.column || (error.column),
189
+ errorType: metadata.errorType || error.name || 'RuntimeError',
190
+ code: metadata.code || 'ERR500',
191
+ context: metadata.context || (metadata.expressionId ? 'Expression: ' + metadata.expressionId : ''),
192
+ hints: metadata.hints || [],
193
+ isProd: false // Check env here if possible
194
+ };
195
+
196
+ // Try to instantiate the user's ErrorPage
197
+ const instance = instantiateComponent('ErrorPage', errorProps, container);
198
+ if (instance) {
199
+ container.innerHTML = '';
200
+ instance.mount();
201
+ } else {
202
+ // Fallback to basic HTML if ErrorPage component fails or is missing
203
+ container.innerHTML = \`
204
+ <div style="padding: 4rem; font-family: system-ui, sans-serif; background: #000; color: #fff; min-h: 100vh;">
205
+ <h1 style="font-size: 3rem; margin-bottom: 1rem; color: #ef4444;">Zenith Runtime Error</h1>
206
+ <p style="font-size: 1.5rem; opacity: 0.8;">\${errorProps.message}</p>
207
+ <pre style="margin-top: 2rem; padding: 1rem; background: #111; border-radius: 8px; overflow: auto; font-size: 0.8rem; color: #888;">\${errorProps.stack}</pre>
208
+ </div>
209
+ \`;
210
+ }
211
+ }
212
+
213
+ function fallbackErrorPage(props, el) {
214
+ // This could be a more complex fallback, but for now we just return null
215
+ // to trigger the basic HTML fallback in renderErrorPage.
216
+ return null;
217
+ }
218
+
219
+ /**
220
+ * Hydrate components by discovering data-zen-component markers
221
+ * This is the ONLY place component instantiation should happen
222
+ */
223
+ function hydrateComponents(container) {
224
+ try {
225
+ const componentElements = container.querySelectorAll('[data-zen-component]');
226
+
227
+ for (let i = 0; i < componentElements.length; i++) {
228
+ const el = componentElements[i];
229
+ const componentName = el.getAttribute('data-zen-component');
230
+
231
+ // Skip if already hydrated OR if handled by instance script (data-zen-inst)
232
+ if (el.__zenith_instance || el.hasAttribute('data-zen-inst')) continue;
233
+
234
+ // Parse props from data attribute if present
235
+ const propsJson = el.getAttribute('data-zen-props') || '{}';
236
+ let props = {};
237
+ try {
238
+ props = JSON.parse(propsJson);
239
+ } catch(e) {
240
+ console.warn('[Zenith] Invalid props JSON for', componentName);
241
+ }
242
+
243
+ try {
244
+ // Instantiate component and bind to DOM element
245
+ const instance = instantiateComponent(componentName, props, el);
246
+
247
+ if (instance) {
248
+ el.__zenith_instance = instance;
249
+ }
250
+ } catch (e) {
251
+ renderErrorPage(e, { component: componentName, props: props });
252
+ }
253
+ }
254
+ } catch (e) {
255
+ renderErrorPage(e, { activity: 'hydrateComponents' });
256
+ }
257
+ }
258
+
259
+ // ============================================
260
+ // Expression Registry & Hydration
261
+ // ============================================
262
+
263
+ const expressionRegistry = new Map();
264
+
265
+ function registerExpression(id, fn) {
266
+ expressionRegistry.set(id, fn);
267
+ }
268
+
269
+ function getExpression(id) {
270
+ return expressionRegistry.get(id);
271
+ }
272
+
273
+ function updateNode(node, exprId, pageState) {
274
+ const expr = getExpression(exprId);
275
+ if (!expr) return;
276
+
277
+ zenEffect(function() {
278
+ try {
279
+ const result = expr(pageState);
280
+
281
+ if (node.hasAttribute('data-zen-text')) {
282
+ // Handle complex text/children results
283
+ if (result === null || result === undefined || result === false) {
284
+ node.textContent = '';
285
+ } else if (typeof result === 'string') {
286
+ if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
287
+ node.innerHTML = result;
288
+ } else {
289
+ node.textContent = result;
290
+ }
291
+ } else if (result instanceof Node) {
292
+ node.innerHTML = '';
293
+ node.appendChild(result);
294
+ } else if (Array.isArray(result)) {
295
+ node.innerHTML = '';
296
+ const fragment = document.createDocumentFragment();
297
+ result.flat(Infinity).forEach(item => {
298
+ if (item instanceof Node) fragment.appendChild(item);
299
+ else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
300
+ });
301
+ node.appendChild(fragment);
302
+ } else {
303
+ node.textContent = String(result);
304
+ }
305
+ } else {
306
+ // Attribute update
307
+ const attrNames = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
308
+ for (const attr of attrNames) {
309
+ if (node.hasAttribute('data-zen-attr-' + attr)) {
310
+ if (attr === 'class' || attr === 'className') {
311
+ node.className = String(result || '');
312
+ } else if (attr === 'disabled' || attr === 'checked') {
313
+ if (result) node.setAttribute(attr, '');
314
+ else node.removeAttribute(attr);
315
+ } else {
316
+ if (result != null && result !== false) node.setAttribute(attr, String(result));
317
+ else node.removeAttribute(attr);
318
+ }
319
+ }
320
+ }
321
+ }
322
+ } catch (e) {
323
+ renderErrorPage(e, { expressionId: exprId, node: node });
324
+ }
325
+ });
326
+ }
327
+
328
+ /**
329
+ * Hydrate a page with reactive bindings
330
+ * Called after page HTML is in DOM
331
+ */
332
+ function updateLoopBinding(template, exprId, pageState) {
333
+ const expr = getExpression(exprId);
334
+ if (!expr) return;
335
+
336
+ const itemVar = template.getAttribute('data-zen-item');
337
+ const indexVar = template.getAttribute('data-zen-index');
338
+
339
+ // Use a marker or a container next to the template to hold instances
340
+ let container = template.__zen_container;
341
+ if (!container) {
342
+ container = document.createElement('div');
343
+ container.style.display = 'contents';
344
+ template.parentNode.insertBefore(container, template.nextSibling);
345
+ template.__zen_container = container;
346
+ }
347
+
348
+ zenEffect(function() {
349
+ try {
350
+ const items = expr(pageState);
351
+ if (!Array.isArray(items)) return;
352
+
353
+ // Simple reconciliation: clear and redraw
354
+ container.innerHTML = '';
355
+
356
+ items.forEach(function(item, index) {
357
+ const fragment = template.content.cloneNode(true);
358
+
359
+ // Create child scope
360
+ const childState = Object.assign({}, pageState);
361
+ if (itemVar) childState[itemVar] = item;
362
+ if (indexVar) childState[indexVar] = index;
363
+
364
+ // Recursive hydration for the fragment
365
+ zenithHydrate(childState, fragment);
366
+
367
+ container.appendChild(fragment);
368
+ });
369
+ } catch (e) {
370
+ renderErrorPage(e, { expressionId: exprId, activity: 'loopReconciliation' });
371
+ }
372
+ });
373
+ }
374
+
375
+ /**
376
+ * Hydrate static HTML with dynamic expressions
377
+ */
378
+ function zenithHydrate(pageState, container) {
379
+ try {
380
+ container = container || document;
381
+
382
+ // Find all text expression placeholders
383
+ const textNodes = container.querySelectorAll('[data-zen-text]');
384
+ textNodes.forEach(el => updateNode(el, el.getAttribute('data-zen-text'), pageState));
385
+
386
+ // Find all attribute expression placeholders
387
+ const attrNodes = container.querySelectorAll('[data-zen-attr-class], [data-zen-attr-style], [data-zen-attr-src], [data-zen-attr-href]');
388
+ attrNodes.forEach(el => {
389
+ const attrMatch = Array.from(el.attributes).find(a => a.name.startsWith('data-zen-attr-'));
390
+ if (attrMatch) updateNode(el, attrMatch.value, pageState);
391
+ });
392
+
393
+ // Find all loop placeholders
394
+ const loopNodes = container.querySelectorAll('template[data-zen-loop]');
395
+ loopNodes.forEach(el => updateLoopBinding(el, el.getAttribute('data-zen-loop'), pageState));
396
+
397
+ // Wire up event handlers
398
+ const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
399
+ eventTypes.forEach(eventType => {
400
+ const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
401
+ elements.forEach(el => {
402
+ const handlerName = el.getAttribute('data-zen-' + eventType);
403
+ if (handlerName && (global[handlerName] || getExpression(handlerName))) {
404
+ el.addEventListener(eventType, function(e) {
405
+ const handler = global[handlerName] || getExpression(handlerName);
406
+ if (typeof handler === 'function') handler(e, el);
407
+ });
408
+ }
409
+ });
410
+ });
411
+
412
+ // Trigger mount (only if container is root)
413
+ if (container === document || container.id === 'app' || container.tagName === 'BODY') {
414
+ triggerMount();
415
+ }
416
+ } catch (e) {
417
+ renderErrorPage(e, { activity: 'zenithHydrate' });
418
+ }
419
+ }
420
+
421
+ // ============================================
422
+ // zenith:content - Content Engine
423
+ // ============================================
424
+
425
+ const schemaRegistry = new Map();
426
+ const builtInEnhancers = {
427
+ readTime: (item) => {
428
+ const wordsPerMinute = 200;
429
+ const text = item.content || '';
430
+ const wordCount = text.split(/\\s+/).length;
431
+ const minutes = Math.ceil(wordCount / wordsPerMinute);
432
+ return Object.assign({}, item, { readTime: minutes + ' min' });
433
+ },
434
+ wordCount: (item) => {
435
+ const text = item.content || '';
436
+ const wordCount = text.split(/\\s+/).length;
437
+ return Object.assign({}, item, { wordCount: wordCount });
438
+ }
439
+ };
440
+
441
+ async function applyEnhancers(item, enhancers) {
442
+ let enrichedItem = Object.assign({}, item);
443
+ for (const enhancer of enhancers) {
444
+ if (typeof enhancer === 'string') {
445
+ const fn = builtInEnhancers[enhancer];
446
+ if (fn) enrichedItem = await fn(enrichedItem);
447
+ } else if (typeof enhancer === 'function') {
448
+ enrichedItem = await enhancer(enrichedItem);
449
+ }
450
+ }
451
+ return enrichedItem;
452
+ }
453
+
454
+ class ZenCollection {
455
+ constructor(items) {
456
+ this.items = [...items];
457
+ this.filters = [];
458
+ this.sortField = null;
459
+ this.sortOrder = 'desc';
460
+ this.limitCount = null;
461
+ this.selectedFields = null;
462
+ this.enhancers = [];
463
+ this._groupByFolder = false;
464
+ }
465
+ where(fn) { this.filters.push(fn); return this; }
466
+ sortBy(field, order = 'desc') { this.sortField = field; this.sortOrder = order; return this; }
467
+ limit(n) { this.limitCount = n; return this; }
468
+ fields(f) { this.selectedFields = f; return this; }
469
+ enhanceWith(e) { this.enhancers.push(e); return this; }
470
+ groupByFolder() { this._groupByFolder = true; return this; }
471
+ get() {
472
+ let results = [...this.items];
473
+ for (const filter of this.filters) results = results.filter(filter);
474
+ if (this.sortField) {
475
+ results.sort((a, b) => {
476
+ const valA = a[this.sortField];
477
+ const valB = b[this.sortField];
478
+ if (valA < valB) return this.sortOrder === 'asc' ? -1 : 1;
479
+ if (valA > valB) return this.sortOrder === 'asc' ? 1 : -1;
480
+ return 0;
481
+ });
482
+ }
483
+ if (this.limitCount !== null) results = results.slice(0, this.limitCount);
484
+
485
+ // Apply enhancers synchronously if possible
486
+ if (this.enhancers.length > 0) {
487
+ results = results.map(item => {
488
+ let enrichedItem = Object.assign({}, item);
489
+ for (const enhancer of this.enhancers) {
490
+ if (typeof enhancer === 'string') {
491
+ const fn = builtInEnhancers[enhancer];
492
+ if (fn) enrichedItem = fn(enrichedItem);
493
+ } else if (typeof enhancer === 'function') {
494
+ enrichedItem = enhancer(enrichedItem);
495
+ }
496
+ }
497
+ return enrichedItem;
498
+ });
499
+ }
500
+
501
+ if (this.selectedFields) {
502
+ results = results.map(item => {
503
+ const newItem = {};
504
+ this.selectedFields.forEach(f => { newItem[f] = item[f]; });
505
+ return newItem;
506
+ });
507
+ }
508
+
509
+ // Group by folder if requested
510
+ if (this._groupByFolder) {
511
+ const groups = {};
512
+ const groupOrder = [];
513
+ for (const item of results) {
514
+ // Extract folder from slug (e.g., "getting-started/installation" -> "getting-started")
515
+ const slug = item.slug || item.id || '';
516
+ const parts = slug.split('/');
517
+ const folder = parts.length > 1 ? parts[0] : 'root';
518
+
519
+ if (!groups[folder]) {
520
+ groups[folder] = {
521
+ id: folder,
522
+ title: folder.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
523
+ items: []
524
+ };
525
+ groupOrder.push(folder);
526
+ }
527
+ groups[folder].items.push(item);
528
+ }
529
+ return groupOrder.map(f => groups[f]);
530
+ }
531
+
532
+ return results;
533
+ }
534
+ }
535
+
536
+ function defineSchema(name, schema) { schemaRegistry.set(name, schema); }
537
+
538
+ function zenCollection(collectionName) {
539
+ // Access plugin data from the neutral envelope
540
+ // Content plugin stores all items under 'content' namespace
541
+ const pluginData = global.__ZENITH_PLUGIN_DATA__ || {};
542
+ const contentItems = pluginData.content || [];
543
+
544
+ // Filter by collection name (plugin owns data structure, runtime just filters)
545
+ const data = Array.isArray(contentItems)
546
+ ? contentItems.filter(item => item && item.collection === collectionName)
547
+ : [];
548
+
549
+ return new ZenCollection(data);
550
+ }
551
+
552
+ // ============================================
553
+ // useZenOrder - Documentation ordering & navigation
554
+ // ============================================
555
+
556
+ function slugify(text) {
557
+ return String(text || '')
558
+ .toLowerCase()
559
+ .replace(/[^\\w\\s-]/g, '')
560
+ .replace(/\\s+/g, '-')
561
+ .replace(/-+/g, '-')
562
+ .trim();
563
+ }
564
+
565
+ function getDocSlug(doc) {
566
+ const slugOrId = String(doc.slug || doc.id || '');
567
+ const parts = slugOrId.split('/');
568
+ const filename = parts[parts.length - 1];
569
+ return filename ? slugify(filename) : slugify(doc.title || 'untitled');
570
+ }
571
+
572
+ function processRawSections(rawSections) {
573
+ const sections = (rawSections || []).map(function(rawSection) {
574
+ const sectionSlug = slugify(rawSection.title || rawSection.id || 'section');
575
+ const items = (rawSection.items || []).map(function(item) {
576
+ return Object.assign({}, item, {
577
+ slug: getDocSlug(item),
578
+ sectionSlug: sectionSlug,
579
+ isIntro: item.intro === true || (item.tags && item.tags.includes && item.tags.includes('intro'))
580
+ });
581
+ });
582
+
583
+ // Sort items: intro first, then order, then alphabetical
584
+ items.sort(function(a, b) {
585
+ if (a.isIntro && !b.isIntro) return -1;
586
+ if (!a.isIntro && b.isIntro) return 1;
587
+ if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
588
+ if (a.order !== undefined) return -1;
589
+ if (b.order !== undefined) return 1;
590
+ return (a.title || '').localeCompare(b.title || '');
591
+ });
592
+
593
+ return {
594
+ id: rawSection.id || sectionSlug,
595
+ title: rawSection.title || 'Untitled',
596
+ slug: sectionSlug,
597
+ order: rawSection.order !== undefined ? rawSection.order : (rawSection.meta && rawSection.meta.order),
598
+ hasIntro: items.some(function(i) { return i.isIntro; }),
599
+ items: items
600
+ };
601
+ });
602
+
603
+ // Sort sections: order → hasIntro → alphabetical
604
+ sections.sort(function(a, b) {
605
+ if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
606
+ if (a.order !== undefined) return -1;
607
+ if (b.order !== undefined) return 1;
608
+ if (a.hasIntro && !b.hasIntro) return -1;
609
+ if (!a.hasIntro && b.hasIntro) return 1;
610
+ return a.title.localeCompare(b.title);
611
+ });
612
+
613
+ return sections;
614
+ }
615
+
616
+ function createZenOrder(rawSections) {
617
+ const sections = processRawSections(rawSections);
618
+
619
+ return {
620
+ sections: sections,
621
+ selectedSection: sections[0] || null,
622
+ selectedDoc: sections[0] && sections[0].items[0] || null,
623
+
624
+ getSectionBySlug: function(sectionSlug) {
625
+ return sections.find(function(s) { return s.slug === sectionSlug; }) || null;
626
+ },
627
+
628
+ getDocBySlug: function(sectionSlug, docSlug) {
629
+ var section = sections.find(function(s) { return s.slug === sectionSlug; });
630
+ if (!section) return null;
631
+ return section.items.find(function(d) { return d.slug === docSlug; }) || null;
632
+ },
633
+
634
+ getNextDoc: function(currentDoc) {
635
+ if (!currentDoc) return null;
636
+ var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
637
+ if (!currentSection) return null;
638
+ var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
639
+ if (idx < currentSection.items.length - 1) return currentSection.items[idx + 1];
640
+ var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
641
+ if (secIdx < sections.length - 1) return sections[secIdx + 1].items[0] || null;
642
+ return null;
643
+ },
644
+
645
+ getPrevDoc: function(currentDoc) {
646
+ if (!currentDoc) return null;
647
+ var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
648
+ if (!currentSection) return null;
649
+ var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
650
+ if (idx > 0) return currentSection.items[idx - 1];
651
+ var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
652
+ if (secIdx > 0) {
653
+ var prev = sections[secIdx - 1];
654
+ return prev.items[prev.items.length - 1] || null;
655
+ }
656
+ return null;
657
+ },
658
+
659
+ buildDocUrl: function(sectionSlug, docSlug) {
660
+ if (!docSlug || docSlug === 'index') return '/documentation/' + sectionSlug;
661
+ return '/documentation/' + sectionSlug + '/' + docSlug;
662
+ }
663
+ };
664
+ }
665
+
666
+ // ============================================
667
+ // Virtual DOM Helper for JSX-style expressions
668
+ // ============================================
669
+
670
+ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
671
+ const SVG_TAGS = new Set([
672
+ 'svg', 'path', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'rect',
673
+ 'g', 'defs', 'clipPath', 'mask', 'use', 'symbol', 'text', 'tspan', 'textPath',
674
+ 'image', 'foreignObject', 'switch', 'desc', 'title', 'metadata', 'linearGradient',
675
+ 'radialGradient', 'stop', 'pattern', 'filter', 'feBlend', 'feColorMatrix',
676
+ 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
677
+ 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge',
678
+ 'feMergeNode', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile',
679
+ 'feTurbulence', 'animate', 'animateMotion', 'animateTransform', 'set', 'marker'
680
+ ]);
681
+
682
+ // Track current namespace context for nested elements
683
+ let currentNamespace = null;
684
+
685
+ function h(tag, props, children) {
686
+ // Determine if this element should be in SVG namespace
687
+ const isSvgTag = SVG_TAGS.has(tag) || SVG_TAGS.has(tag.toLowerCase());
688
+ const useSvgNamespace = isSvgTag || currentNamespace === SVG_NAMESPACE;
689
+
690
+ // Create element with correct namespace
691
+ const el = useSvgNamespace
692
+ ? document.createElementNS(SVG_NAMESPACE, tag)
693
+ : document.createElement(tag);
694
+
695
+ // Track namespace context for children (save previous, set new if svg root)
696
+ const previousNamespace = currentNamespace;
697
+ if (tag === 'svg' || tag === 'SVG') {
698
+ currentNamespace = SVG_NAMESPACE;
699
+ }
700
+
701
+ if (props) {
702
+ // Helper to set class for both HTML and SVG elements
703
+ const setClass = (element, value) => {
704
+ if (useSvgNamespace && 'className' in element && typeof element.className === 'object') {
705
+ element.className.baseVal = String(value || '');
706
+ } else {
707
+ element.className = String(value || '');
708
+ }
709
+ };
710
+
711
+ for (const [key, value] of Object.entries(props)) {
712
+ if (key.startsWith('on') && typeof value === 'function') {
713
+ el.addEventListener(key.slice(2).toLowerCase(), value);
714
+ } else if (key === 'class' || key === 'className') {
715
+ if (typeof value === 'function') {
716
+ // Reactive class binding
717
+ zenEffect(function() {
718
+ setClass(el, value());
719
+ });
720
+ } else {
721
+ setClass(el, value);
722
+ }
723
+ } else if (key === 'style' && typeof value === 'object') {
724
+ Object.assign(el.style, value);
725
+ } else if (typeof value === 'function') {
726
+ // Reactive attribute binding
727
+ zenEffect(function() {
728
+ const v = value();
729
+ if (v != null && v !== false) {
730
+ el.setAttribute(key, String(v));
731
+ } else {
732
+ el.removeAttribute(key);
733
+ }
734
+ });
735
+ } else if (value != null && value !== false) {
736
+ el.setAttribute(key, String(value));
737
+ }
738
+ }
739
+ }
740
+
741
+ if (children != null && children !== false) {
742
+ // Flatten nested arrays (from .map() calls)
743
+ const childrenArray = Array.isArray(children) ? children.flat(Infinity) : [children];
744
+ for (const child of childrenArray) {
745
+ // Skip null, undefined, and false
746
+ if (child == null || child === false) continue;
747
+
748
+ if (typeof child === 'function') {
749
+ // Reactive child - use a placeholder and update reactively
750
+ const placeholder = document.createComment('expr');
751
+ el.appendChild(placeholder);
752
+ let currentNodes = [];
753
+
754
+ zenEffect(function() {
755
+ const result = child();
756
+ // Remove old nodes
757
+ for (let i = 0; i < currentNodes.length; i++) {
758
+ const n = currentNodes[i];
759
+ if (n.parentNode) n.parentNode.removeChild(n);
760
+ }
761
+ currentNodes = [];
762
+
763
+ if (result == null || result === false) {
764
+ // Render nothing
765
+ } else if (result instanceof Node) {
766
+ placeholder.parentNode.insertBefore(result, placeholder);
767
+ currentNodes = [result];
768
+ } else if (Array.isArray(result)) {
769
+ // Array of nodes/strings
770
+ const flat = result.flat(Infinity);
771
+ for (let i = 0; i < flat.length; i++) {
772
+ const item = flat[i];
773
+ if (item == null || item === false) continue;
774
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
775
+ placeholder.parentNode.insertBefore(node, placeholder);
776
+ currentNodes.push(node);
777
+ }
778
+ } else {
779
+ // Primitive (string, number)
780
+ const textNode = document.createTextNode(String(result));
781
+ placeholder.parentNode.insertBefore(textNode, placeholder);
782
+ currentNodes = [textNode];
783
+ }
784
+ });
785
+ } else {
786
+ // Static child
787
+ el.appendChild(child instanceof Node ? child : document.createTextNode(String(child)));
788
+ }
789
+ }
790
+ }
791
+
792
+ // Restore previous namespace context
793
+ currentNamespace = previousNamespace;
794
+
795
+ return el;
796
+ }
797
+
798
+
799
+ // ============================================
800
+ // Export to window.__zenith
801
+ // ============================================
802
+
803
+ global.__zenith = {
804
+ // Reactivity primitives
805
+ signal: zenSignal,
806
+ state: zenState,
807
+ effect: zenEffect,
808
+ memo: zenMemo,
809
+ ref: zenRef,
810
+ batch: zenBatch,
811
+ untrack: zenUntrack,
812
+ // zenith:content
813
+ defineSchema: defineSchema,
814
+ zenCollection: zenCollection,
815
+ // useZenOrder hook
816
+ createZenOrder: createZenOrder,
817
+ processRawSections: processRawSections,
818
+ slugify: slugify,
819
+ // Virtual DOM helper for JSX
820
+ h: h,
821
+ // Lifecycle
822
+ onMount: zenOnMount,
823
+ onUnmount: zenOnUnmount,
824
+ // Internal hooks
825
+ triggerMount: triggerMount,
826
+ triggerUnmount: triggerUnmount,
827
+ // Hydration
828
+ hydrate: zenithHydrate,
829
+ hydrateComponents: hydrateComponents, // Marker-driven component instantiation
830
+ registerExpression: registerExpression,
831
+ getExpression: getExpression,
832
+ // Component instance system
833
+ createInstance: createComponentInstance,
834
+ defineComponent: defineComponent,
835
+ instantiate: instantiateComponent
836
+ };
837
+
838
+ // Expose with zen* prefix for direct usage
839
+ global.zenSignal = zenSignal;
840
+ global.zenState = zenState;
841
+ global.zenEffect = zenEffect;
842
+ global.zenMemo = zenMemo;
843
+ global.zenRef = zenRef;
844
+ global.zenBatch = zenBatch;
845
+ global.zenUntrack = zenUntrack;
846
+ global.zenOnMount = zenOnMount;
847
+ global.zenOnUnmount = zenOnUnmount;
848
+ global.zenithHydrate = zenithHydrate;
849
+
850
+ // Clean aliases
851
+ global.signal = zenSignal;
852
+ global.state = zenState;
853
+ global.effect = zenEffect;
854
+ global.memo = zenMemo;
855
+ global.ref = zenRef;
856
+ global.batch = zenBatch;
857
+ global.untrack = zenUntrack;
858
+ global.onMount = zenOnMount;
859
+ global.onUnmount = zenOnUnmount;
860
+
861
+ // useZenOrder hook exports
862
+ global.createZenOrder = createZenOrder;
863
+ global.processRawSections = processRawSections;
864
+ global.slugify = slugify;
865
+
866
+ // ============================================
867
+ // SPA Router Runtime
868
+ // ============================================
869
+
870
+ // Router state
871
+ // Current route state
872
+ var currentRoute = {
873
+ path: '/',
874
+ params: {},
875
+ query: {}
876
+ };
877
+
878
+ // Route listeners
879
+ var routeListeners = new Set();
880
+
881
+ // Router outlet element
882
+ var routerOutlet = null;
883
+
884
+ // Page modules registry
885
+ var pageModules = {};
886
+
887
+ // Route manifest
888
+ var routeManifest = [];
889
+
890
+ /**
891
+ * Parse query string
892
+ */
893
+ function parseQueryString(search) {
894
+ var query = {};
895
+ if (!search || search === '?') return query;
896
+ var params = new URLSearchParams(search);
897
+ params.forEach(function(value, key) { query[key] = value; });
898
+ return query;
899
+ }
900
+
901
+ // ============================================
902
+ // Global Error Listeners (Dev Mode)
903
+ // ============================================
904
+
905
+ window.onerror = function(message, source, lineno, colno, error) {
906
+ renderErrorPage(error || new Error(message), {
907
+ file: source,
908
+ line: lineno,
909
+ column: colno,
910
+ errorType: 'UncaughtError'
911
+ });
912
+ return false;
913
+ };
914
+
915
+ window.onunhandledrejection = function(event) {
916
+ renderErrorPage(event.reason || new Error('Unhandled Promise Rejection'), {
917
+ errorType: 'UnhandledRejection'
918
+ });
919
+ };
920
+
921
+ /**
922
+ * Resolve route from pathname
923
+ */
924
+ function resolveRoute(pathname) {
925
+ var normalizedPath = pathname === '' ? '/' : pathname;
926
+
927
+ for (var i = 0; i < routeManifest.length; i++) {
928
+ var route = routeManifest[i];
929
+ var match = route.regex.exec(normalizedPath);
930
+ if (match) {
931
+ var params = {};
932
+ for (var j = 0; j < route.paramNames.length; j++) {
933
+ var paramValue = match[j + 1];
934
+ if (paramValue !== undefined) {
935
+ params[route.paramNames[j]] = decodeURIComponent(paramValue);
936
+ }
937
+ }
938
+ return { record: route, params: params };
939
+ }
940
+ }
941
+ return null;
942
+ }
943
+
944
+ /**
945
+ * Clean up previous page
946
+ */
947
+ function cleanupPreviousPage() {
948
+ // Trigger unmount lifecycle hooks
949
+ if (global.__zenith && global.__zenith.triggerUnmount) {
950
+ global.__zenith.triggerUnmount();
951
+ }
952
+
953
+ // Remove previous page styles
954
+ var prevStyles = document.querySelectorAll('style[data-zen-page-style]');
955
+ prevStyles.forEach(function(s) { s.remove(); });
956
+
957
+ // Clean up window properties
958
+ if (global.__zenith_cleanup) {
959
+ global.__zenith_cleanup.forEach(function(key) {
960
+ try { delete global[key]; } catch(e) {}
961
+ });
962
+ }
963
+ global.__zenith_cleanup = [];
964
+ }
965
+
966
+ /**
967
+ * Inject styles
968
+ */
969
+ function injectStyles(styles) {
970
+ styles.forEach(function(content, i) {
971
+ var style = document.createElement('style');
972
+ style.setAttribute('data-zen-page-style', String(i));
973
+ style.textContent = content;
974
+ document.head.appendChild(style);
975
+ });
976
+ }
977
+
978
+ /**
979
+ * Execute scripts
980
+ */
981
+ function executeScripts(scripts) {
982
+ scripts.forEach(function(content) {
983
+ try {
984
+ var fn = new Function(content);
985
+ fn();
986
+ } catch (e) {
987
+ console.error('[Zenith Router] Script error:', e);
988
+ }
989
+ });
990
+ }
991
+
992
+ /**
993
+ * Render page
994
+ */
995
+ function renderPage(pageModule) {
996
+ if (!routerOutlet) {
997
+ console.warn('[Zenith Router] No router outlet');
998
+ return;
999
+ }
1000
+
1001
+ cleanupPreviousPage();
1002
+ routerOutlet.innerHTML = pageModule.html;
1003
+ injectStyles(pageModule.styles);
1004
+ executeScripts(pageModule.scripts);
1005
+
1006
+ // Trigger mount lifecycle hooks after scripts are executed
1007
+ if (global.__zenith && global.__zenith.triggerMount) {
1008
+ global.__zenith.triggerMount();
1009
+ }
1010
+ }
1011
+
1012
+ /**
1013
+ * Notify listeners
1014
+ */
1015
+ function notifyListeners(route, prevRoute) {
1016
+ routeListeners.forEach(function(listener) {
1017
+ try { listener(route, prevRoute); } catch(e) {}
1018
+ });
1019
+ }
1020
+
1021
+ /**
1022
+ * Resolve and render
1023
+ */
1024
+ function resolveAndRender(path, query, updateHistory, replace) {
1025
+ replace = replace || false;
1026
+ var prevRoute = Object.assign({}, currentRoute);
1027
+ var resolved = resolveRoute(path);
1028
+
1029
+ if (resolved) {
1030
+ currentRoute = {
1031
+ path: path,
1032
+ params: resolved.params,
1033
+ query: query,
1034
+ matched: resolved.record
1035
+ };
1036
+
1037
+ var pageModule = pageModules[resolved.record.path];
1038
+ if (pageModule) {
1039
+ renderPage(pageModule);
1040
+ }
1041
+ } else {
1042
+ currentRoute = { path: path, params: {}, query: query, matched: undefined };
1043
+ console.warn('[Zenith Router] No route matched:', path);
1044
+
1045
+ // Render 404 if available
1046
+ if (routerOutlet) {
1047
+ routerOutlet.innerHTML = '<div style="padding: 2rem; text-align: center;"><h1>404</h1><p>Page not found</p></div>';
1048
+ }
1049
+ }
1050
+
1051
+ if (updateHistory) {
1052
+ var url = path + (Object.keys(query).length ? '?' + new URLSearchParams(query) : '');
1053
+ if (replace) {
1054
+ history.replaceState(null, '', url);
1055
+ } else {
1056
+ history.pushState(null, '', url);
1057
+ }
1058
+ }
1059
+
1060
+ notifyListeners(currentRoute, prevRoute);
1061
+ global.__zenith_route = currentRoute;
1062
+ }
1063
+
1064
+ /**
1065
+ * Handle popstate
1066
+ */
1067
+ function handlePopState() {
1068
+ resolveAndRender(
1069
+ location.pathname,
1070
+ parseQueryString(location.search),
1071
+ false,
1072
+ false
1073
+ );
1074
+ }
1075
+
1076
+ /**
1077
+ * Navigate (public API)
1078
+ */
1079
+ function navigate(to, options) {
1080
+ options = options || {};
1081
+ var path, query = {};
1082
+
1083
+ if (to.includes('?')) {
1084
+ var parts = to.split('?');
1085
+ path = parts[0];
1086
+ query = parseQueryString('?' + parts[1]);
1087
+ } else {
1088
+ path = to;
1089
+ }
1090
+
1091
+ if (!path.startsWith('/')) {
1092
+ var currentDir = currentRoute.path.split('/').slice(0, -1).join('/');
1093
+ path = currentDir + '/' + path;
1094
+ }
1095
+
1096
+ var normalizedPath = path === '' ? '/' : path;
1097
+ var currentPath = currentRoute.path === '' ? '/' : currentRoute.path;
1098
+ var isSamePath = normalizedPath === currentPath;
1099
+
1100
+ if (isSamePath && JSON.stringify(query) === JSON.stringify(currentRoute.query)) {
1101
+ return;
1102
+ }
1103
+
1104
+ // Dev mode: If no route manifest is loaded, use browser navigation
1105
+ // This allows ZenLink to work in dev server where pages are served fresh
1106
+ if (routeManifest.length === 0) {
1107
+ var url = normalizedPath + (Object.keys(query).length ? '?' + new URLSearchParams(query) : '');
1108
+ if (options.replace) {
1109
+ location.replace(url);
1110
+ } else {
1111
+ location.href = url;
1112
+ }
1113
+ return;
1114
+ }
1115
+
1116
+ resolveAndRender(path, query, true, options.replace || false);
1117
+ }
1118
+
1119
+ /**
1120
+ * Get current route
1121
+ */
1122
+ function getRoute() {
1123
+ return Object.assign({}, currentRoute);
1124
+ }
1125
+
1126
+ /**
1127
+ * Subscribe to route changes
1128
+ */
1129
+ function onRouteChange(listener) {
1130
+ routeListeners.add(listener);
1131
+ return function() { routeListeners.delete(listener); };
1132
+ }
1133
+
1134
+ /**
1135
+ * Check if path is active
1136
+ */
1137
+ function isActive(path, exact) {
1138
+ if (exact) return currentRoute.path === path;
1139
+ return currentRoute.path.startsWith(path);
1140
+ }
1141
+
1142
+ /**
1143
+ * Prefetch a route
1144
+ */
1145
+ var prefetchedRoutes = new Set();
1146
+ function prefetch(path) {
1147
+ var normalizedPath = path === '' ? '/' : path;
1148
+
1149
+ if (prefetchedRoutes.has(normalizedPath)) {
1150
+ return Promise.resolve();
1151
+ }
1152
+ prefetchedRoutes.add(normalizedPath);
1153
+
1154
+ var resolved = resolveRoute(normalizedPath);
1155
+ if (!resolved) {
1156
+ return Promise.resolve();
1157
+ }
1158
+
1159
+ // In SPA build, all modules are already loaded
1160
+ return Promise.resolve();
1161
+ }
1162
+
1163
+ /**
1164
+ * Initialize router
1165
+ */
1166
+ function initRouter(manifest, modules, outlet) {
1167
+ routeManifest = manifest;
1168
+ Object.assign(pageModules, modules);
1169
+
1170
+ if (outlet) {
1171
+ routerOutlet = typeof outlet === 'string'
1172
+ ? document.querySelector(outlet)
1173
+ : outlet;
1174
+ }
1175
+
1176
+ window.addEventListener('popstate', handlePopState);
1177
+
1178
+ // Initial route resolution
1179
+ resolveAndRender(
1180
+ location.pathname,
1181
+ parseQueryString(location.search),
1182
+ false
1183
+ );
1184
+ }
1185
+
1186
+ // Expose router API globally
1187
+ global.__zenith_router = {
1188
+ navigate: navigate,
1189
+ getRoute: getRoute,
1190
+ onRouteChange: onRouteChange,
1191
+ isActive: isActive,
1192
+ prefetch: prefetch,
1193
+ initRouter: initRouter
1194
+ };
1195
+
1196
+ // Also expose navigate directly for convenience
1197
+ global.navigate = navigate;
1198
+ global.zenCollection = zenCollection;
1199
+
1200
+
1201
+ // ============================================
1202
+ // HMR Client (Development Only)
1203
+ // ============================================
1204
+
1205
+ if (typeof window !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {
1206
+ let socket;
1207
+ function connectHMR() {
1208
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
1209
+ socket = new WebSocket(protocol + '//' + location.host + '/hmr');
1210
+
1211
+ socket.onmessage = function(event) {
1212
+ try {
1213
+ const data = JSON.parse(event.data);
1214
+ if (data.type === 'reload') {
1215
+ console.log('[Zenith] HMR: Reloading page...');
1216
+ location.reload();
1217
+ } else if (data.type === 'style-update') {
1218
+ console.log('[Zenith] HMR: Updating style ' + data.url);
1219
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
1220
+ for (let i = 0; i < links.length; i++) {
1221
+ const link = links[i];
1222
+ const url = new URL(link.href);
1223
+ if (url.pathname === data.url) {
1224
+ link.href = data.url + '?t=' + Date.now();
1225
+ break;
1226
+ }
1227
+ }
1228
+ }
1229
+ } catch (e) {
1230
+ console.error('[Zenith] HMR Error:', e);
1231
+ }
1232
+ };
1233
+
1234
+ socket.onclose = function() {
1235
+ console.log('[Zenith] HMR: Connection closed. Retrying in 2s...');
1236
+ setTimeout(connectHMR, 2000);
1237
+ };
1238
+ }
1239
+
1240
+ // Connect unless explicitly disabled
1241
+ if (!window.__ZENITH_NO_HMR__) {
1242
+ connectHMR();
1243
+ }
1244
+ }
1245
+
1246
+ })(typeof window !== 'undefined' ? window : this);
1247
+ `;
1248
+ }
1249
+ /**
1250
+ * Generate a minified version of the bundle
1251
+ * For production builds
1252
+ */
1253
+ export function generateMinifiedBundleJS() {
1254
+ // For now, return non-minified
1255
+ // TODO: Add minification via terser or similar
1256
+ return generateBundleJS();
1257
+ }
1258
+ /**
1259
+ * Get bundle version for cache busting
1260
+ */
1261
+ export function getBundleVersion() {
1262
+ return '0.1.0';
1263
+ }