window.__canonRuntime = {
_init_count: 0, _replay_count: 0, _observer_events: 0,
_inited_uids: new Set(), _mod: null,
_timeline: [], _max_timeline: 200,
_event(type, data) {
const entry = { t: Date.now(), type, ...data };
this._timeline.push(entry);
if (this._timeline.length > this._max_timeline) this._timeline.shift();
},
_trackInit(uid) {
const is_replay = uid && this._inited_uids.has(uid);
if (is_replay) this._replay_count++;
if (uid) this._inited_uids.add(uid);
this._init_count++;
this._event('init', { uid: uid || 'global', replay: is_replay });
},
_trackObserver(added_count) {
this._observer_events++;
this._event('observer', { added: added_count || 0 });
},
_trackDispatch(group, uid) { this._event('dispatch', { group, uid }); },
_setMod(mod) { this._mod = mod; this._event('boot', { msg: 'wasm loaded' }); },
get init_count() { return this._init_count; },
get replay_count() { return this._replay_count; },
get observer_events() { return this._observer_events; },
get active_listeners() { try { return this._mod?.runtime_active_listeners?.() ?? -1; } catch(e) { return -1; } },
get orphan_listeners() { try { return this._mod?.runtime_orphan_listeners?.() ?? -1; } catch(e) { return -1; } },
get initialized_count() { try { return this._mod?.runtime_initialized_count?.() ?? -1; } catch(e) { return -1; } },
get namespaces() { try { return Array.from(this._mod?.runtime_namespaces?.() ?? []); } catch(e) { return []; } },
lifecycle(uid) { try { return this._mod?.runtime_lifecycle_state?.(uid) ?? 'unknown'; } catch(e) { return 'unknown'; } },
ownership(uid) { try { return this._mod?.runtime_ownership_summary?.(uid) ?? 'not_registered'; } catch(e) { return 'not_registered'; } },
get total_resources() { try { return this._mod?.runtime_total_resources?.() ?? 'unavailable'; } catch(e) { return 'unavailable'; } },
snapshot() {
return {
init_count: this.init_count, replay_count: this.replay_count,
observer_events: this.observer_events, active_listeners: this.active_listeners,
orphan_listeners: this.orphan_listeners, initialized_count: this.initialized_count,
namespaces: this.namespaces,
};
},
timeline() { return [...this._timeline]; },
events(type) { return type ? this._timeline.filter(e => e.type === type) : [...this._timeline]; },
replays() { return this._timeline.filter(e => e.type === 'init' && e.replay); },
observers() { return this._timeline.filter(e => e.type === 'observer'); },
listeners() { return this._timeline.filter(e => e.type === 'listener'); },
inits() { return this._timeline.filter(e => e.type === 'init'); },
init_frequency(window_ms) {
const now = Date.now();
return this._timeline.filter(e => e.type === 'init' && (now - e.t) < window_ms).length;
},
trace(n) {
const events = this._timeline.slice(-(n || 20));
return events.map(e => {
const dt = e.t - (this._timeline[0]?.t || e.t);
switch(e.type) {
case 'boot': return `+${dt}ms [BOOT] ${e.msg}`;
case 'init': return `+${dt}ms [INIT] uid=${e.uid}${e.replay ? ' REPLAY!' : ''}`;
case 'dispatch': return `+${dt}ms [DISPATCH] group=${e.group} uid=${e.uid}`;
case 'observer': return `+${dt}ms [OBSERVER] added=${e.added}`;
default: return `+${dt}ms [${e.type.toUpperCase()}] ${JSON.stringify(e)}`;
}
}).join('\n');
}
};
window.__canonGroups = {
_loaded: {}, _mods: {},
async load(group) {
if (this._loaded[group]) return this._mods[group];
await import('/js/wasm_hash.js').catch(() => {});
const hash = window.__CANON_WASM_HASH__;
const base = `/wasm/${group}`;
const js = `${base}/canonrs_interactions_${group}.js`;
const wasm = `${base}/canonrs_interactions_${group}_bg.wasm?v=${hash}`;
try {
const mod = await import(js);
await mod.default({ module_or_path: wasm });
this._loaded[group] = true; this._mods[group] = mod;
console.log(`[canonrs] group loaded: ${group}`);
return mod;
} catch(e) { console.warn(`[canonrs] group unavailable: ${group}`, e); return null; }
},
async initGroup(group) {
const mod = await this.load(group);
if (mod && typeof mod[`init_${group}_all`] === 'function') mod[`init_${group}_all`]();
}
};
(async () => {
try {
const base = '/wasm';
await import('/js/wasm_hash.js').catch(() => {});
const hash = window.__CANON_WASM_HASH__;
if (!hash) throw new Error('[canonrs] wasm hash missing — run orchestrator first');
const js = `${base}/canonrs_interactions.js`;
const wasm = `${base}/canonrs_interactions_bg.wasm?v=${hash}`;
const mod = await import(js);
await mod.default({ module_or_path: wasm });
const initObserver = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.type === 'attributes' && m.attributeName === 'data-rs-initialized') {
const uid = m.target.getAttribute('data-rs-uid') || 'unknown';
const group = m.target.getAttribute('data-rs-interaction') || 'unknown';
window.__canonRuntime._trackDispatch(group, uid);
}
}
});
initObserver.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['data-rs-initialized'] });
mod.init_all();
window.__canonRuntime._setMod(mod);
window.__canonrs_init_all__ = () => { mod.init_all(); window.__canonRuntime._trackInit(null); };
setInterval(() => { if (mod.gc) mod.gc(); }, 30000);
console.log(`[canonrs] runtime ready — v0.1.0 hash=${hash}`);
const GROUPS = ['init', 'nav', 'data', 'gesture', 'overlay', 'selection', 'content'];
for (const group of GROUPS) { window.__canonGroups.load(group).catch(() => {}); }
} catch (e) {
console.error('[canonrs] failed to load runtime', e);
}
})();
(function() {
let rafPending = false;
const observer = new MutationObserver((mutations) => {
const added_count = mutations.reduce((n, m) => n + m.addedNodes.length, 0);
window.__canonRuntime._trackObserver(added_count);
const hasNew = mutations.some(m =>
Array.from(m.addedNodes).some(n => n.nodeType === 1 &&
!(n.closest && n.closest('[data-rs-inline-editing]'))
)
);
if (hasNew && !rafPending) {
rafPending = true;
requestAnimationFrame(() => {
if (window.__canonrs_init_all__) { window.__canonrs_init_all__(); }
rafPending = false;
});
}
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', () => {
observer.observe(document.body, { childList: true, subtree: true });
});
}
})();
(function canonReload(delay) {
const es = new EventSource('/canon-reload');
es.onmessage = () => { console.log('[canonrs] reload triggered'); location.reload(); };
es.onopen = () => { console.log('[canonrs] reload connected'); };
es.onerror = () => {
es.close();
const next = Math.min((delay || 1000) * 2, 30000);
setTimeout(() => canonReload(next), next);
};
})(1000);