@zenithbuild/runtime 0.2.0 → 0.5.0-beta.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HYDRATION_CONTRACT.md +98 -0
- package/README.md +3 -0
- package/RUNTIME_CONTRACT.md +183 -0
- package/dist/cleanup.js +88 -0
- package/dist/diagnostics.js +309 -0
- package/dist/effect.js +3 -0
- package/dist/events.js +59 -0
- package/dist/hydrate.js +1596 -0
- package/dist/index.js +4 -0
- package/dist/ref.js +23 -0
- package/dist/runtime.js +57 -0
- package/dist/signal.js +58 -0
- package/dist/state.js +79 -0
- package/dist/template.js +360 -0
- package/dist/zeneffect.js +640 -0
- package/package.json +19 -6
- package/src/dom-hydration.ts +0 -297
- package/src/index.ts +0 -488
- package/src/thin-runtime.ts +0 -159
- package/tsconfig.json +0 -28
package/dist/index.js
ADDED
package/dist/ref.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ref.js — Zenith Runtime ref primitive
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// A structural DOM pointer. NOT reactive.
|
|
5
|
+
// ref.current is a plain property — no tracking, no proxies, no subscriptions.
|
|
6
|
+
//
|
|
7
|
+
// Contract:
|
|
8
|
+
// - ref() returns { current: null }
|
|
9
|
+
// - ref(initialValue) returns { current: initialValue }
|
|
10
|
+
// - .current is assigned by runtime at mount, before zenMount callbacks run
|
|
11
|
+
// - .current is set to null on component disposal
|
|
12
|
+
// - Reading .current does NOT register a dependency in zenEffect
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a deterministic ref for structural DOM binding.
|
|
16
|
+
*
|
|
17
|
+
* @template T
|
|
18
|
+
* @param {T} [initialValue]
|
|
19
|
+
* @returns {{ current: T | null }}
|
|
20
|
+
*/
|
|
21
|
+
export function ref(initialValue) {
|
|
22
|
+
return { current: initialValue ?? null };
|
|
23
|
+
}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// runtime.js — Zenith Runtime V0
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Backward-compatible mount wrapper around explicit hydrate(payload).
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
import { hydrate } from './hydrate.js';
|
|
8
|
+
import { cleanup } from './cleanup.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mount a page module that already contains deterministic binding tables.
|
|
12
|
+
*
|
|
13
|
+
* @param {HTMLElement} container
|
|
14
|
+
* @param {object | function} pageModule
|
|
15
|
+
* @returns {() => void}
|
|
16
|
+
*/
|
|
17
|
+
export function mount(container, pageModule) {
|
|
18
|
+
if (!container || typeof container.querySelectorAll !== 'function') {
|
|
19
|
+
throw new Error('[Zenith Runtime] mount(container, pageModule) requires a DOM container');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pageFactory = _resolvePageFactory(pageModule);
|
|
23
|
+
if (!pageFactory) {
|
|
24
|
+
throw new Error('[Zenith Runtime] mount(container, pageModule) requires default() or __zenith_page()');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const page = pageFactory();
|
|
28
|
+
if (!page || typeof page.html !== 'string') {
|
|
29
|
+
throw new Error('[Zenith Runtime] __zenith_page() must return an object with html');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
container.innerHTML = page.html;
|
|
33
|
+
|
|
34
|
+
hydrate({
|
|
35
|
+
root: container,
|
|
36
|
+
ir_version: page.ir_version,
|
|
37
|
+
expressions: Array.isArray(page.expressions) ? page.expressions : [],
|
|
38
|
+
markers: Array.isArray(page.markers) ? page.markers : [],
|
|
39
|
+
events: Array.isArray(page.events) ? page.events : [],
|
|
40
|
+
state_values: Array.isArray(page.state_values) ? page.state_values : [],
|
|
41
|
+
signals: Array.isArray(page.signals) ? page.signals : [],
|
|
42
|
+
components: Array.isArray(page.components) ? page.components : []
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return function unmount() {
|
|
46
|
+
cleanup();
|
|
47
|
+
container.innerHTML = '';
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function _resolvePageFactory(pageModule) {
|
|
52
|
+
if (typeof pageModule === 'function') return pageModule;
|
|
53
|
+
if (!pageModule || typeof pageModule !== 'object') return null;
|
|
54
|
+
if (typeof pageModule.default === 'function') return pageModule.default;
|
|
55
|
+
if (typeof pageModule.__zenith_page === 'function') return pageModule.__zenith_page;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
package/dist/signal.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// signal.js — Zenith Runtime V0
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Minimal explicit signal primitive.
|
|
5
|
+
//
|
|
6
|
+
// API:
|
|
7
|
+
// const count = signal(0);
|
|
8
|
+
// count.get();
|
|
9
|
+
// count.set(1);
|
|
10
|
+
// const unsubscribe = count.subscribe((value) => { ... });
|
|
11
|
+
//
|
|
12
|
+
// Constraints:
|
|
13
|
+
// - No proxy
|
|
14
|
+
// - No implicit dependency tracking
|
|
15
|
+
// - No scheduler
|
|
16
|
+
// - No async queue
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a deterministic signal with explicit subscription semantics.
|
|
21
|
+
*
|
|
22
|
+
* @param {*} initialValue
|
|
23
|
+
* @returns {{ get: () => *, set: (next: *) => *, subscribe: (fn: (value: *) => void) => () => void }}
|
|
24
|
+
*/
|
|
25
|
+
export function signal(initialValue) {
|
|
26
|
+
let value = initialValue;
|
|
27
|
+
const subscribers = new Set();
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
get() {
|
|
31
|
+
return value;
|
|
32
|
+
},
|
|
33
|
+
set(nextValue) {
|
|
34
|
+
if (Object.is(value, nextValue)) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
value = nextValue;
|
|
39
|
+
|
|
40
|
+
const snapshot = [...subscribers];
|
|
41
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
42
|
+
snapshot[i](value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return value;
|
|
46
|
+
},
|
|
47
|
+
subscribe(fn) {
|
|
48
|
+
if (typeof fn !== 'function') {
|
|
49
|
+
throw new Error('[Zenith Runtime] signal.subscribe(fn) requires a function');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
subscribers.add(fn);
|
|
53
|
+
return function unsubscribe() {
|
|
54
|
+
subscribers.delete(fn);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// state.js — Zenith Runtime V0
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Proxy-free immutable state helper.
|
|
5
|
+
//
|
|
6
|
+
// API:
|
|
7
|
+
// const store = state({ count: 0 });
|
|
8
|
+
// store.get();
|
|
9
|
+
// store.set({ count: 1 });
|
|
10
|
+
// store.set((prev) => ({ ...prev, count: prev.count + 1 }));
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
import { _nextReactiveId, _trackDependency } from './zeneffect.js';
|
|
13
|
+
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function cloneSnapshot(value) {
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return [...value];
|
|
24
|
+
}
|
|
25
|
+
if (isPlainObject(value)) {
|
|
26
|
+
return { ...value };
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a proxy-free immutable state container.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} initialValue
|
|
35
|
+
* @returns {{ get: () => object, set: (patch: object | ((prev: object) => object)) => object, subscribe: (fn: (next: object) => void) => () => void }}
|
|
36
|
+
*/
|
|
37
|
+
export function state(initialValue) {
|
|
38
|
+
let current = Object.freeze(cloneSnapshot(initialValue));
|
|
39
|
+
const subscribers = new Set();
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
get() {
|
|
43
|
+
return current;
|
|
44
|
+
},
|
|
45
|
+
set(nextPatch) {
|
|
46
|
+
const nextValue = typeof nextPatch === 'function'
|
|
47
|
+
? nextPatch(current)
|
|
48
|
+
: { ...current, ...nextPatch };
|
|
49
|
+
|
|
50
|
+
if (!nextValue || typeof nextValue !== 'object' || Array.isArray(nextValue)) {
|
|
51
|
+
throw new Error('[Zenith Runtime] state.set(next) must resolve to a plain object');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const nextSnapshot = Object.freeze(cloneSnapshot(nextValue));
|
|
55
|
+
if (Object.is(current, nextSnapshot)) {
|
|
56
|
+
return current;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
current = nextSnapshot;
|
|
60
|
+
|
|
61
|
+
const snapshot = [...subscribers];
|
|
62
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
63
|
+
snapshot[i](current);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return current;
|
|
67
|
+
},
|
|
68
|
+
subscribe(fn) {
|
|
69
|
+
if (typeof fn !== 'function') {
|
|
70
|
+
throw new Error('[Zenith Runtime] state.subscribe(fn) requires a function');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
subscribers.add(fn);
|
|
74
|
+
return function unsubscribe() {
|
|
75
|
+
subscribers.delete(fn);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
function normalizeNewlines(value) {
|
|
9
|
+
return String(value).replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readRuntimeSourceFile(fileName) {
|
|
13
|
+
const fullPath = join(__dirname, fileName);
|
|
14
|
+
return normalizeNewlines(readFileSync(fullPath, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function stripImports(source) {
|
|
18
|
+
return source.replace(/^\s*import\s+[^;]+;\s*$/gm, '').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildRuntimeModuleSource() {
|
|
22
|
+
const segments = [
|
|
23
|
+
stripImports(readRuntimeSourceFile('zeneffect.js')),
|
|
24
|
+
stripImports(readRuntimeSourceFile('ref.js')),
|
|
25
|
+
stripImports(readRuntimeSourceFile('signal.js')),
|
|
26
|
+
stripImports(readRuntimeSourceFile('state.js')),
|
|
27
|
+
stripImports(readRuntimeSourceFile('diagnostics.js')),
|
|
28
|
+
stripImports(readRuntimeSourceFile('cleanup.js')),
|
|
29
|
+
stripImports(readRuntimeSourceFile('hydrate.js'))
|
|
30
|
+
].filter(Boolean);
|
|
31
|
+
|
|
32
|
+
return normalizeNewlines(segments.join('\n\n'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const RUNTIME_MODULE_SOURCE = buildRuntimeModuleSource();
|
|
36
|
+
|
|
37
|
+
export function runtimeModuleSource() {
|
|
38
|
+
return RUNTIME_MODULE_SOURCE;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const RUNTIME_DEV_CLIENT_SOURCE = `(() => {
|
|
42
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
43
|
+
if (window.__zenithDevClientActive === true) return;
|
|
44
|
+
window.__zenithDevClientActive = true;
|
|
45
|
+
|
|
46
|
+
const DEV_KEY = '__ZENITH_DEV__';
|
|
47
|
+
const STORAGE_KEY = '__ZENITH_DEBUG__';
|
|
48
|
+
const DEFAULT_LOGS = { route: false, bindings: false, events: false, hmr: false };
|
|
49
|
+
|
|
50
|
+
function readStoredLogs() {
|
|
51
|
+
try {
|
|
52
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
53
|
+
if (!raw) return { ...DEFAULT_LOGS };
|
|
54
|
+
if (raw === '1') return { route: true, bindings: true, events: true, hmr: true };
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
if (!parsed || typeof parsed !== 'object') return { ...DEFAULT_LOGS };
|
|
57
|
+
return {
|
|
58
|
+
route: parsed.route === true,
|
|
59
|
+
bindings: parsed.bindings === true,
|
|
60
|
+
events: parsed.events === true,
|
|
61
|
+
hmr: parsed.hmr === true
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return { ...DEFAULT_LOGS };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function persistLogs(logs) {
|
|
69
|
+
try {
|
|
70
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(logs));
|
|
71
|
+
} catch {
|
|
72
|
+
// ignore storage failures in private mode
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ensureDevState() {
|
|
77
|
+
const current = window[DEV_KEY] && typeof window[DEV_KEY] === 'object' ? window[DEV_KEY] : {};
|
|
78
|
+
const logs = current.logs && typeof current.logs === 'object' ? current.logs : readStoredLogs();
|
|
79
|
+
const overlay = current.overlay && typeof current.overlay === 'object' ? current.overlay : {};
|
|
80
|
+
const state = {
|
|
81
|
+
logs: {
|
|
82
|
+
route: logs.route === true,
|
|
83
|
+
bindings: logs.bindings === true,
|
|
84
|
+
events: logs.events === true,
|
|
85
|
+
hmr: logs.hmr === true
|
|
86
|
+
},
|
|
87
|
+
overlay: {
|
|
88
|
+
open: overlay.open === true
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
window[DEV_KEY] = state;
|
|
92
|
+
persistLogs(state.logs);
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseEventData(raw) {
|
|
97
|
+
if (typeof raw !== 'string' || raw.length === 0) return {};
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(raw);
|
|
100
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
101
|
+
} catch {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function fetchDevState() {
|
|
107
|
+
return fetch('/__zenith_dev/state', { cache: 'no-store' })
|
|
108
|
+
.then(function (response) {
|
|
109
|
+
if (!response || !response.ok) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return response.json().catch(function () { return null; });
|
|
113
|
+
})
|
|
114
|
+
.then(function (payload) {
|
|
115
|
+
return payload && typeof payload === 'object' ? payload : null;
|
|
116
|
+
})
|
|
117
|
+
.catch(function () {
|
|
118
|
+
return null;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function swapStylesheet(nextHref) {
|
|
123
|
+
if (typeof nextHref !== 'string' || nextHref.length === 0) {
|
|
124
|
+
window.location.reload();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
|
|
128
|
+
if (links.length === 0) {
|
|
129
|
+
window.location.reload();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const separator = nextHref.includes('?') ? '&' : '?';
|
|
133
|
+
links[0].setAttribute('href', nextHref + separator + '__zenith_dev=' + Date.now());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const state = ensureDevState();
|
|
137
|
+
const shell = document.createElement('div');
|
|
138
|
+
shell.setAttribute('data-zenith-dev-overlay', 'true');
|
|
139
|
+
shell.style.position = 'fixed';
|
|
140
|
+
shell.style.left = '12px';
|
|
141
|
+
shell.style.bottom = '12px';
|
|
142
|
+
shell.style.zIndex = '2147483647';
|
|
143
|
+
shell.style.fontFamily = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
|
|
144
|
+
shell.style.fontSize = '12px';
|
|
145
|
+
shell.style.pointerEvents = 'none';
|
|
146
|
+
|
|
147
|
+
const pill = document.createElement('button');
|
|
148
|
+
pill.type = 'button';
|
|
149
|
+
pill.textContent = 'Zenith Dev';
|
|
150
|
+
pill.style.pointerEvents = 'auto';
|
|
151
|
+
pill.style.border = '1px solid rgba(255,255,255,0.2)';
|
|
152
|
+
pill.style.background = 'rgba(20,20,24,0.88)';
|
|
153
|
+
pill.style.color = '#ecf2ff';
|
|
154
|
+
pill.style.borderRadius = '999px';
|
|
155
|
+
pill.style.padding = '6px 10px';
|
|
156
|
+
pill.style.cursor = 'pointer';
|
|
157
|
+
pill.style.boxShadow = '0 8px 24px rgba(0,0,0,0.25)';
|
|
158
|
+
|
|
159
|
+
const panel = document.createElement('div');
|
|
160
|
+
panel.style.display = state.overlay.open ? 'block' : 'none';
|
|
161
|
+
panel.style.width = '360px';
|
|
162
|
+
panel.style.marginTop = '8px';
|
|
163
|
+
panel.style.pointerEvents = 'auto';
|
|
164
|
+
panel.style.background = 'rgba(14,16,20,0.94)';
|
|
165
|
+
panel.style.color = '#dbe6ff';
|
|
166
|
+
panel.style.border = '1px solid rgba(255,255,255,0.16)';
|
|
167
|
+
panel.style.borderRadius = '10px';
|
|
168
|
+
panel.style.padding = '10px';
|
|
169
|
+
panel.style.boxShadow = '0 14px 30px rgba(0,0,0,0.35)';
|
|
170
|
+
|
|
171
|
+
const status = document.createElement('div');
|
|
172
|
+
status.textContent = 'status: connecting';
|
|
173
|
+
status.style.marginBottom = '6px';
|
|
174
|
+
|
|
175
|
+
const info = document.createElement('div');
|
|
176
|
+
info.textContent = 'route: ' + window.location.pathname;
|
|
177
|
+
info.style.opacity = '0.85';
|
|
178
|
+
info.style.marginBottom = '8px';
|
|
179
|
+
info.style.whiteSpace = 'pre-wrap';
|
|
180
|
+
|
|
181
|
+
const controls = document.createElement('div');
|
|
182
|
+
controls.style.display = 'grid';
|
|
183
|
+
controls.style.gridTemplateColumns = '1fr 1fr';
|
|
184
|
+
controls.style.gap = '6px';
|
|
185
|
+
controls.style.marginBottom = '8px';
|
|
186
|
+
|
|
187
|
+
function makeButton(label) {
|
|
188
|
+
const button = document.createElement('button');
|
|
189
|
+
button.type = 'button';
|
|
190
|
+
button.textContent = label;
|
|
191
|
+
button.style.border = '1px solid rgba(255,255,255,0.2)';
|
|
192
|
+
button.style.borderRadius = '6px';
|
|
193
|
+
button.style.padding = '6px 8px';
|
|
194
|
+
button.style.cursor = 'pointer';
|
|
195
|
+
button.style.background = 'rgba(36,41,51,0.85)';
|
|
196
|
+
button.style.color = '#ecf2ff';
|
|
197
|
+
return button;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const reloadButton = makeButton('Reload');
|
|
201
|
+
const copyButton = makeButton('Copy URL');
|
|
202
|
+
const debugButton = makeButton('Toggle Debug');
|
|
203
|
+
const overlayButton = makeButton('Toggle Overlay');
|
|
204
|
+
|
|
205
|
+
controls.append(reloadButton, copyButton, debugButton, overlayButton);
|
|
206
|
+
|
|
207
|
+
const logs = document.createElement('pre');
|
|
208
|
+
logs.style.margin = '0';
|
|
209
|
+
logs.style.padding = '8px';
|
|
210
|
+
logs.style.maxHeight = '190px';
|
|
211
|
+
logs.style.overflow = 'auto';
|
|
212
|
+
logs.style.border = '1px solid rgba(255,255,255,0.12)';
|
|
213
|
+
logs.style.borderRadius = '6px';
|
|
214
|
+
logs.style.background = 'rgba(8,10,14,0.8)';
|
|
215
|
+
logs.style.whiteSpace = 'pre-wrap';
|
|
216
|
+
logs.textContent = '[zenith-dev] waiting for server events...';
|
|
217
|
+
|
|
218
|
+
panel.append(status, info, controls, logs);
|
|
219
|
+
shell.append(pill, panel);
|
|
220
|
+
|
|
221
|
+
function setOpen(open) {
|
|
222
|
+
state.overlay.open = open === true;
|
|
223
|
+
panel.style.display = state.overlay.open ? 'block' : 'none';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function appendLog(line) {
|
|
227
|
+
logs.textContent += '\\n' + line;
|
|
228
|
+
logs.scrollTop = logs.scrollHeight;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function updateInfo(payload) {
|
|
232
|
+
const route = typeof payload.route === 'string' ? payload.route : window.location.pathname;
|
|
233
|
+
const hash = typeof payload.buildHash === 'string' ? payload.buildHash : 'n/a';
|
|
234
|
+
const durationValue = Number.isFinite(payload.durationMs) ? payload.durationMs : payload.lastBuildMs;
|
|
235
|
+
const duration = Number.isFinite(durationValue) ? durationValue + 'ms' : 'n/a';
|
|
236
|
+
const changed = Array.isArray(payload.changedFiles) ? payload.changedFiles.join(', ') : '';
|
|
237
|
+
const serverUrl = typeof payload.serverUrl === 'string' ? payload.serverUrl : window.location.origin;
|
|
238
|
+
const buildId = Number.isInteger(payload.buildId) ? payload.buildId : 'n/a';
|
|
239
|
+
const buildStatus = typeof payload.status === 'string' ? payload.status : 'unknown';
|
|
240
|
+
info.textContent =
|
|
241
|
+
'server: ' + serverUrl + '\\n' +
|
|
242
|
+
'route: ' + route + '\\n' +
|
|
243
|
+
'buildId: ' + buildId + '\\n' +
|
|
244
|
+
'status: ' + buildStatus + '\\n' +
|
|
245
|
+
'hash: ' + hash + '\\n' +
|
|
246
|
+
'duration: ' + duration + '\\n' +
|
|
247
|
+
'changed: ' + changed;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function allLogsEnabled() {
|
|
251
|
+
return state.logs.route && state.logs.bindings && state.logs.events && state.logs.hmr;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function setAllLogs(enabled) {
|
|
255
|
+
state.logs.route = enabled;
|
|
256
|
+
state.logs.bindings = enabled;
|
|
257
|
+
state.logs.events = enabled;
|
|
258
|
+
state.logs.hmr = enabled;
|
|
259
|
+
persistLogs(state.logs);
|
|
260
|
+
appendLog('[zenith-dev] debug logs ' + (enabled ? 'enabled' : 'disabled'));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function emitDebug(label, payload) {
|
|
264
|
+
if (state.logs.hmr === true) {
|
|
265
|
+
console.log('[zenith-dev] ' + label, payload);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
pill.addEventListener('click', function () {
|
|
270
|
+
setOpen(!state.overlay.open);
|
|
271
|
+
});
|
|
272
|
+
overlayButton.addEventListener('click', function () {
|
|
273
|
+
setOpen(!state.overlay.open);
|
|
274
|
+
});
|
|
275
|
+
reloadButton.addEventListener('click', function () {
|
|
276
|
+
window.location.reload();
|
|
277
|
+
});
|
|
278
|
+
copyButton.addEventListener('click', function () {
|
|
279
|
+
const target = window.location.origin;
|
|
280
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
281
|
+
navigator.clipboard.writeText(target).catch(() => {});
|
|
282
|
+
}
|
|
283
|
+
appendLog('[zenith-dev] copied ' + target);
|
|
284
|
+
});
|
|
285
|
+
debugButton.addEventListener('click', function () {
|
|
286
|
+
setAllLogs(!allLogsEnabled());
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
function mount() {
|
|
290
|
+
if (document.body) {
|
|
291
|
+
document.body.appendChild(shell);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (document.readyState === 'loading') {
|
|
295
|
+
document.addEventListener('DOMContentLoaded', mount, { once: true });
|
|
296
|
+
} else {
|
|
297
|
+
mount();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const source = new EventSource('/__zenith_dev/events');
|
|
301
|
+
source.addEventListener('connected', function (event) {
|
|
302
|
+
const payload = parseEventData(event.data);
|
|
303
|
+
status.textContent = 'status: connected';
|
|
304
|
+
updateInfo(payload);
|
|
305
|
+
appendLog('[connected] dev channel online');
|
|
306
|
+
emitDebug('connected', payload);
|
|
307
|
+
fetchDevState().then(function (statePayload) {
|
|
308
|
+
if (!statePayload) return;
|
|
309
|
+
updateInfo({ ...payload, ...statePayload });
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
source.addEventListener('build_start', function (event) {
|
|
313
|
+
const payload = parseEventData(event.data);
|
|
314
|
+
status.textContent = 'status: rebuilding';
|
|
315
|
+
appendLog('[build_start] ' + (Array.isArray(payload.changedFiles) ? payload.changedFiles.join(', ') : ''));
|
|
316
|
+
emitDebug('build_start', payload);
|
|
317
|
+
});
|
|
318
|
+
source.addEventListener('build_complete', function (event) {
|
|
319
|
+
const payload = parseEventData(event.data);
|
|
320
|
+
status.textContent = 'status: ready';
|
|
321
|
+
updateInfo(payload);
|
|
322
|
+
appendLog('[build_complete] ' + (Number.isFinite(payload.durationMs) ? payload.durationMs + 'ms' : 'done'));
|
|
323
|
+
emitDebug('build_complete', payload);
|
|
324
|
+
});
|
|
325
|
+
source.addEventListener('build_error', function (event) {
|
|
326
|
+
const payload = parseEventData(event.data);
|
|
327
|
+
status.textContent = 'status: error';
|
|
328
|
+
appendLog('[build_error] ' + (payload.message || 'Unknown error'));
|
|
329
|
+
emitDebug('build_error', payload);
|
|
330
|
+
});
|
|
331
|
+
source.addEventListener('reload', function (event) {
|
|
332
|
+
const payload = parseEventData(event.data);
|
|
333
|
+
appendLog('[reload] refreshing page');
|
|
334
|
+
emitDebug('reload', payload);
|
|
335
|
+
setTimeout(function () {
|
|
336
|
+
window.location.reload();
|
|
337
|
+
}, 30);
|
|
338
|
+
});
|
|
339
|
+
source.addEventListener('css_update', function (event) {
|
|
340
|
+
const payload = parseEventData(event.data);
|
|
341
|
+
appendLog('[css_update] ' + (payload.href || ''));
|
|
342
|
+
emitDebug('css_update', payload);
|
|
343
|
+
fetchDevState().then(function (statePayload) {
|
|
344
|
+
if (statePayload && typeof statePayload.cssHref === 'string' && statePayload.cssHref.length > 0) {
|
|
345
|
+
updateInfo({ ...payload, ...statePayload });
|
|
346
|
+
swapStylesheet(statePayload.cssHref);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
swapStylesheet(payload.href);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
source.addEventListener('error', function () {
|
|
353
|
+
status.textContent = 'status: disconnected';
|
|
354
|
+
appendLog('[error] lost dev server connection');
|
|
355
|
+
});
|
|
356
|
+
})();`;
|
|
357
|
+
|
|
358
|
+
export function runtimeDevClientSource() {
|
|
359
|
+
return RUNTIME_DEV_CLIENT_SOURCE;
|
|
360
|
+
}
|