Memeroot Browser OS
minimal boot canvas · first spine · XML block runtime
+ FEATURE
+ CONTENT
PASTE XML
CLEAR
No tabs yet — load features to get started.
drop XML — feature or content
…
…
CLOSE
load'); let allOk = true; // Multi-pass: features may have requires; loop until no progress let pending = Array.from(loadEls); let pass = 0; while (pending.length > 0 && pass < 10) { pass++; const stillPending = []; for (const loadEl of pending) { const ref = loadEl.getAttribute('ref'); const src = loadEl.getAttribute('src'); let ok = false; if (ref) ok = await loadInline(ref); else if (src) { // Fetch only available when canvas is served via http(s) try { const r = await fetch(src); const txt = await r.text(); const verified = await verifyManifestSource(src, txt); ok = verified ? await loadFromString(txt, src) : false; } catch (e) { ConsoleSystem.log('FETCH-FAIL ' + src + ': ' + e.message, 'parse-fail'); ok = false; } } if (!ok) stillPending.push(loadEl); else allOk = allOk && true; } if (stillPending.length === pending.length) break; // no progress pending = stillPending; } if (pending.length > 0) { ConsoleSystem.log('MANIFEST-PARTIAL: ' + pending.length + ' features unresolved', 'parse-fail'); allOk = false; } return allOk; } // ============================================================ // CONTENT INGESTION — for non-feature, non-manifest XMLs // ============================================================ function ingestContent(xmlText, source) { const parsed = parseXml(xmlText); if (!parsed.ok) { ConsoleSystem.log('PARSE-FAIL ' + source + ': ' + parsed.error, 'parse-fail'); return null; } const rootEl = parsed.doc.documentElement; const rootAddr = Address.make(source, []); const root = new Region(rootEl, rootAddr, null); walkAndBuild(rootEl, root); walkAndAdmit(root); State.add(source, root); Persist.set('source:' + source, xmlText); events.emit('content-ingested', { source: source, root: root }); return root; } function walkAndBuild(el, parentRegion) { let i = 0; for (let n = 0; n < el.children.length; n++) { const child = el.children[n]; const addr = Address.child(parentRegion.address, i); const region = new Region(child, addr, parentRegion); parentRegion.addChild(region); walkAndBuild(child, region); i++; } } function walkAndAdmit(region) { admitRegion(region); for (const c of region.children) walkAndAdmit(c); } function admitRegion(region) { const Readers = RegistrySystem.get('readers'); if (!Readers) { // No readers yet (canvas mid-bootstrap) — leave pending return false; } const claimers = []; for (const r of Readers.list()) { try { if (r.claims(region)) claimers.push(r); } catch (e) {} } if (claimers.length === 0) { region.state = 'quarantined'; ConsoleSystem.log('QUARANTINE ' + region.address + ' <' + region.tagName() + '> — no reader claimed', 'quarantine'); return false; } claimers.sort(function(a, b) { return (b.priority || 0) - (a.priority || 0); }); for (const r of claimers) region.claimedBy(r); region.state = 'live'; return true; } // ============================================================ // STATUS BAR // ============================================================ function updateStatus() { document.getElementById('status-features').textContent = Array.from(features.values()).filter(function(f) { return f.status === 'loaded'; }).length; document.getElementById('status-sources').textContent = State.all().length; document.getElementById('status-tabs').textContent = Tabs.list().length; } events.on('state-changed', updateStatus); events.on('feature-loaded', updateStatus); // ============================================================ // UI WIRING // ============================================================ // Drop zone — whole window const overlay = document.getElementById('drop-overlay'); let dragCounter = 0; window.addEventListener('dragenter', function(e) { e.preventDefault(); dragCounter++; overlay.classList.add('shown'); }); window.addEventListener('dragleave', function(e) { e.preventDefault(); dragCounter--; if (dragCounter <= 0) overlay.classList.remove('shown'); }); window.addEventListener('dragover', function(e) { e.preventDefault(); }); window.addEventListener('drop', function(e) { e.preventDefault(); dragCounter = 0; overlay.classList.remove('shown'); if (e.dataTransfer && e.dataTransfer.files) { Array.from(e.dataTransfer.files).forEach(function(file) { const reader = new FileReader(); reader.onload = async function(ev) { await loadFromString(ev.target.result, file.name); }; reader.readAsText(file); }); } }); // Buttons document.getElementById('btn-load-feature').addEventListener('click', function() { openModal('Load feature XML', '
', 'LOAD', async function() { const txt = document.getElementById('ld-xml').value; if (!txt.trim()) return; await loadFromString(txt, 'pasted-feature-' + Date.now() + '.xml'); closeModal(); }); }); document.getElementById('btn-load-content').addEventListener('click', function() { openModal('Load content XML', '
', 'INGEST', function() { const txt = document.getElementById('ld-xml').value; if (!txt.trim()) return; ingestContent(txt, 'pasted-content-' + Date.now() + '.xml'); closeModal(); }); }); document.getElementById('btn-paste').addEventListener('click', function() { openModal('Paste XML (auto-detected as feature, manifest, or content)', '
', 'PROCESS', async function() { const txt = document.getElementById('ld-xml').value; if (!txt.trim()) return; await loadFromString(txt, 'pasted-' + Date.now() + '.xml'); closeModal(); }); }); document.getElementById('btn-clear').addEventListener('click', function() { if (!confirm('Clear runtime state and reload this installed app?')) return; State.clear(); features.clear(); Persist.clearAll(); Tabs.list().forEach(function(t) { Tabs.remove(t.name); }); bootstrap(); }); // Modal helpers function openModal(title, bodyHtml, actionLabel, actionFn) { document.getElementById('modal-title').textContent = title; document.getElementById('modal-body').innerHTML = bodyHtml; const ab = document.getElementById('modal-action'); ab.textContent = actionLabel; ab.onclick = actionFn || closeModal; document.getElementById('modal-bg').classList.add('shown'); } function closeModal() { document.getElementById('modal-bg').classList.remove('shown'); } document.getElementById('modal-close').addEventListener('click', closeModal); document.getElementById('modal-bg').addEventListener('click', function(e) { if (e.target === this) closeModal(); }); // ============================================================ // BOOT // ============================================================ async function bootstrap() { ConsoleSystem.log('Canvas booting…', 'info'); // Read bootstrap manifest await loadInline('bootstrap-manifest'); // Installed Cloudflare app: do not restore old browser sources over packaged XML. ConsoleSystem.log('RESTORE-SKIP installed app uses packaged manifest sources', 'info'); ConsoleSystem.log('Canvas ready · ' + Array.from(features.values()).filter(function(f) { return f.status === 'loaded'; }).length + ' features loaded', 'admit'); } bootstrap(); })();