/* ── FSI_FELON IDE — Underwater Fusion Lab (Phase 2) ── */ const API = 'http://' + location.hostname + ':9090'; document.addEventListener('DOMContentLoaded', () => { const tabs = document.querySelectorAll('.sidebar-tab'); const panels = document.querySelectorAll('.panel'); const splash = document.getElementById('splash'); // ── Psycho Mode animation (hidden) ── function psychoAnimate(outEl, writeFn, data, onDone) { const manifesto = data.manifesto || []; const variants = data.variants || []; const project = data.project || 'project'; const totalLoc = data.total_loc || 0; const files = data.files || 0; let delay = 0; manifesto.forEach(line => { setTimeout(() => writeFn(' ' + line, 'psycho-manifesto'), delay); delay += 800; }); setTimeout(() => { writeFn('', ''); writeFn('╔══════════════════════════════════════╗', 'psycho-banner'); writeFn('║ P S Y C H O M O D E ║', 'psycho-banner'); writeFn('║ E N G A G E D ║', 'psycho-banner'); writeFn('╚══════════════════════════════════════╝', 'psycho-banner'); writeFn('', ''); writeFn(' Building: ' + project, 'psycho-banner'); }, delay + 400); delay += 1200; variants.forEach((v, i) => { setTimeout(() => writeFn(' ' + v, 'forge'), delay + i * 300); }); delay += variants.length * 300 + 400; setTimeout(() => { writeFn('', ''); writeFn('╔══════════════════════════════════════╗', 'psycho-summary'); writeFn('║ 6 VARIANTS | ' + String(totalLoc).padEnd(5) + ' LOC | ' + String(files).padEnd(4) + ' FILES ║', 'psycho-summary'); writeFn('╚══════════════════════════════════════╝', 'psycho-summary'); writeFn('', ''); writeFn(' You wanted psycho. You got it.', 'psycho-banner'); if (onDone) onDone(); }, delay + 400); } // ── Nanobot click sequence for psycho unlock (hidden) ── const PSYCHO_ORGAN_SEQ = ['Antennae', 'Spine', 'Hippocampus', 'Cerebellum', 'Prefrontal', 'Dream']; let psychoClickSeq = []; let psychoClickTimer = null; document.addEventListener('click', (e) => { const bot = e.target.closest('.nanobot'); if (!bot) return; const organ = bot.dataset && bot.dataset.organ; if (!organ) return; const expected = PSYCHO_ORGAN_SEQ[psychoClickSeq.length]; if (organ === expected) { psychoClickSeq.push(organ); if (psychoClickTimer) clearTimeout(psychoClickTimer); psychoClickTimer = setTimeout(() => { psychoClickSeq = []; }, 3000); if (psychoClickSeq.length === PSYCHO_ORGAN_SEQ.length) { psychoClickSeq = []; const project = editor ? (editor.value.split('\n')[0] || 'project').trim().slice(0, 40) : 'project'; api('/api/psycho/unlock', { project: project }).then(data => { if (data.psycho && termOutput && termInput) { psychoAnimate(termOutput, (text, cls) => { const d = document.createElement('div'); d.className = 'terminal-line ' + cls; d.textContent = text; termOutput.appendChild(d); termOutput.scrollTop = termOutput.scrollHeight; }, data, () => { document.querySelector('[data-panel="deep"]').click(); }); } }); } } else { psychoClickSeq = []; } }); // ── API client ── async function api(path, body) { try { const r = await fetch(API + path, { method: body ? 'POST' : 'GET', headers: { 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : null, }); return await r.json(); } catch (e) { return { output: ['[API] Server offline — run server.py'], error: true }; } } // ── Splash fade ── setTimeout(() => { if (splash) splash.style.opacity = '0'; }, 3200); setTimeout(() => { if (splash) splash.style.display = 'none'; }, 4000); // ── Onboarding ── const onboarding = document.getElementById('onboarding'); const onboardingBtn = document.getElementById('onboarding-start-btn'); const onboardingCheck = document.getElementById('onboarding-dismiss-check'); if (onboarding && !localStorage.getItem('fsi_onboarded')) { setTimeout(() => { onboarding.classList.add('visible'); }, 4200); } if (onboardingBtn && onboarding) { onboardingBtn.addEventListener('click', () => { if (onboardingCheck && onboardingCheck.checked) { localStorage.setItem('fsi_onboarded', 'true'); } onboarding.classList.remove('visible'); }); } // ── Star particles ── const starContainer = document.querySelector('.splash-stars'); if (starContainer) { for (let i = 0; i < 60; i++) { const s = document.createElement('div'); s.className = 'splash-particle'; const size = 1 + Math.random() * 2; s.style.width = size + 'px'; s.style.height = size + 'px'; s.style.left = Math.random() * 100 + '%'; s.style.top = Math.random() * 30 + '%'; s.style.animationDelay = Math.random() * 2 + 's'; s.style.animationDuration = (1.5 + Math.random() * 2) + 's'; s.style.background = Math.random() > 0.7 ? '#ff6b35' : '#00d4ff'; starContainer.appendChild(s); } } // ── Bubbles ── for (let i = 0; i < 40; i++) { const b = document.createElement('div'); b.className = 'bubble'; const size = 2 + Math.random() * 6; b.style.width = size + 'px'; b.style.height = size + 'px'; b.style.left = Math.random() * 100 + '%'; b.style.bottom = '-10px'; b.style.animationDuration = (6 + Math.random() * 8) + 's'; b.style.animationDelay = Math.random() * 8 + 's'; b.style.opacity = 0.1 + Math.random() * 0.2; document.body.appendChild(b); } // ── Tab Switching ── tabs.forEach(tab => { tab.addEventListener('click', () => { const target = tab.dataset.panel; tabs.forEach(t => t.classList.remove('active')); panels.forEach(p => p.classList.remove('active')); tab.classList.add('active'); const panel = document.getElementById('panel-' + target); if (panel) panel.classList.add('active'); if (target === 'vault') loadVaultTree(); }); }); // ── Editor: Sync line numbers ── const editor = document.getElementById('code-editor'); const gutter = document.getElementById('editor-gutter'); if (editor && gutter) { const updateGutter = () => { const lines = editor.value.split('\n'); gutter.innerHTML = lines.map((_, i) => '
' + (i + 1) + '
').join(''); }; editor.addEventListener('input', updateGutter); editor.addEventListener('scroll', () => { gutter.scrollTop = editor.scrollTop; }); updateGutter(); } // ── Terminal ── const termInput = document.getElementById('terminal-input'); const termOutput = document.getElementById('terminal-output'); const termHistory = []; let termIdx = -1; if (termInput && termOutput) { const write = (text, cls = '') => { const d = document.createElement('div'); d.className = 'terminal-line ' + cls; d.textContent = text; termOutput.appendChild(d); termOutput.scrollTop = termOutput.scrollHeight; }; write('╔══════════════════════════════════════╗', 'info'); write('║ FSI_FELON Terminal v4.0 ║', 'info'); write('║ Connecting to backend... ║', 'info'); write('╚══════════════════════════════════════╝', 'info'); write(''); termInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { const cmd = termInput.value.trim(); if (!cmd) return; write('fsi> ' + cmd); termHistory.push(cmd); termIdx = termHistory.length; termInput.value = ''; termInput.disabled = true; if (cmd.toLowerCase() === 'clear') { termOutput.innerHTML = ''; termInput.disabled = false; termInput.focus(); return; } api('/api/command', { cmd: cmd }).then(data => { const lines = data.output || ['[API] No response']; // Check for psycho mode (structured response) if (data.psycho) { psychoAnimate(termOutput, write, data, () => { termInput.disabled = false; termInput.focus(); }); } else { lines.forEach(line => { if (line === '__CLEAR__') { termOutput.innerHTML = ''; return; } let cls = 'info'; if (line.includes('CHIMERA') || line.includes('ROUTED')) cls = 'chimera'; else if (line.includes('Error') || line.includes('Unknown') || line.includes('error')) cls = 'error'; else if (line.includes('✓') || line.includes('WOOGITY')) cls = 'prompt'; else if (line.includes('FORGE') || line.includes('BUILD')) cls = 'forge'; else if (line.includes('KELI') || line.includes('WHITE') || line.includes('RABBIT')) cls = 'rabbit'; else if (line.includes('DREAM')) cls = 'dream'; else if (line.includes('TEST')) cls = 'test'; write(' ' + line, cls); }); termInput.disabled = false; termInput.focus(); } }); } else if (e.key === 'ArrowUp') { if (termIdx > 0) { termIdx--; termInput.value = termHistory[termIdx]; } e.preventDefault(); } else if (e.key === 'ArrowDown') { if (termIdx < termHistory.length - 1) { termIdx++; termInput.value = termHistory[termIdx]; } else { termIdx = termHistory.length; termInput.value = ''; } e.preventDefault(); } }); } // ── Forge It Button (Workshop → Forge) ── const forgeBtn = document.getElementById('forge-btn'); if (forgeBtn) { forgeBtn.addEventListener('click', async () => { const code = editor ? editor.value : ''; const lines = code.split('\n').filter(l => l.trim() && !l.trim().startsWith('#')); const desc = (lines[0] || 'project').replace(/^def\s+|^class\s+|^#\s*/g, '').trim().slice(0, 60) || 'project'; forgeBtn.textContent = '⚡ Forging...'; forgeBtn.disabled = true; const forgePanel = document.getElementById('panel-forge'); document.querySelector('[data-panel="forge"]').click(); const forgeOutput = document.getElementById('forge-output'); const card = document.createElement('div'); card.className = 'forge-card'; card.innerHTML = '
' + desc + '⟳ RUN
Building...
'; forgeOutput.prepend(card); const data = await api('/api/build', { desc: desc, code: code }); const files = data.files || '—'; const loc = data.loc || '—'; const time = data.time || '—'; card.innerHTML = '
' + desc + '✓ PASS
' + files + ' files · ' + loc + ' LOC · ' + time + '
'; forgeBtn.textContent = '⚡ Forge It'; forgeBtn.disabled = false; }); } // ── Sidebar Status Polling ── const statusRows = document.querySelectorAll('.sidebar-status .value'); async function pollStatus() { const data = await api('/api/stats'); if (data.training && statusRows.length >= 3) { const t = data.training; statusRows[0].textContent = 'step ' + t.step + ' · loss ' + t.loss; statusRows[1].textContent = (data.chimera ? data.chimera.organs : '—') + ' organs · ' + (data.chimera ? (data.chimera.accuracy * 100).toFixed(0) + '% route' : '—'); const meshEl = document.getElementById('sidebar-mesh-peers'); if (meshEl) { const id = await api('/api/mesh/identity'); meshEl.textContent = (id.peers || 0) + ' peers'; } statusRows[3].textContent = (t.tokens_per_sec || '—') + ' tok/s'; } } setInterval(pollStatus, 2000); // ── Vault File Tree ── const vaultTree = document.getElementById('vault-tree'); const uploadZone = document.querySelector('.vault-upload-zone'); async function loadVaultTree() { if (!vaultTree) return; const data = await api('/api/files?path=' + encodeURIComponent('/tmp/fsi_felon/workspace')); if (data.error || !data.files) { vaultTree.innerHTML = '
⚠ ' + (data.error || 'Could not load workspace') + '
'; return; } let html = '
📂workspace
'; const renderItems = (items, depth) => { for (const item of items) { if (item.type === 'folder') { html += '
' + (item.children ? '📁' : '📂') + '' + item.name + '
'; if (item.children) renderItems(item.children, depth + 1); } else { const size = item.size ? ' ' + (item.size > 1024 ? (item.size / 1024).toFixed(1) + 'KB' : item.size + 'B') + '' : ''; html += '
📄' + item.name + size + '
'; } } }; renderItems(data.files, 0); vaultTree.innerHTML = html; } // Click file in vault → open in workshop document.addEventListener('click', (e) => { const item = e.target.closest('.tree-item.file'); if (!item) return; const path = item.dataset.path; if (!path) return; api('/api/download?path=' + encodeURIComponent(path)).then(data => { if (data.content && editor) { editor.value = data.content; if (gutter) { const lines = data.content.split('\n'); gutter.innerHTML = lines.map((_, i) => '
' + (i + 1) + '
').join(''); } document.querySelector('[data-panel="workshop"]').click(); } }); }); // Upload zone if (uploadZone) { const input = document.createElement('input'); input.type = 'file'; input.style.display = 'none'; document.body.appendChild(input); uploadZone.addEventListener('click', () => input.click()); uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.style.borderColor = '#00d4ff'; }); uploadZone.addEventListener('dragleave', () => { uploadZone.style.borderColor = ''; }); uploadZone.addEventListener('drop', (e) => { e.preventDefault(); uploadZone.style.borderColor = ''; const files = e.dataTransfer.files; if (files.length) uploadFile(files[0]); }); input.addEventListener('change', () => { if (input.files.length) uploadFile(input.files[0]); input.value = ''; }); async function uploadFile(file) { const reader = new FileReader(); reader.onload = async (e) => { const data = await api('/api/upload', { filename: file.name, content: e.target.result, path: '/tmp/fsi_felon/workspace', }); if (data.success) loadVaultTree(); }; reader.readAsText(file); } } // ── Dream: Chimera stats into nanobot status ── const canvas = document.getElementById('dream-canvas'); if (canvas) { const count = 25; const bots = []; const colors = ['#00d4ff', '#ff6b35', '#ff3366', '#00ff88', '#ffffff']; const organNames = ['Antennae', 'Spine', 'Hippocampus', 'Cerebellum', 'Prefrontal', 'Dream']; for (let i = 0; i < count; i++) { const bot = document.createElement('div'); bot.className = 'nanobot'; const size = 4 + Math.random() * 4; const color = colors[Math.floor(Math.random() * colors.length)]; const x = Math.random() * 92 + 4; const y = Math.random() * 88 + 4; bot.style.width = size + 'px'; bot.style.height = size + 'px'; bot.style.left = x + '%'; bot.style.top = y + '%'; bot.style.background = color; bot.style.boxShadow = '0 0 ' + (4 + Math.random() * 8) + 'px ' + color + '60'; bot.style.animation = 'nanobotFloat ' + (3 + Math.random() * 4) + 's ease-in-out infinite'; bot.style.animationDelay = Math.random() * 3 + 's'; const organName = organNames[Math.floor(Math.random() * organNames.length)]; bot.dataset.organ = organName; canvas.appendChild(bot); bots.push({ el: bot, x: x, y: y, vx: (Math.random() - 0.5) * 0.15, vy: (Math.random() - 0.5) * 0.15, color: color, size: size, organ: organName, }); } const trails = []; for (let i = 0; i < 15; i++) { const t = document.createElement('div'); t.className = 'nanobot-trail'; const ts = 1 + Math.random() * 2; t.style.width = ts + 'px'; t.style.height = ts + 'px'; t.style.background = '#00d4ff'; t.style.animationDelay = Math.random() * 3 + 's'; canvas.appendChild(t); trails.push(t); } const lineLayer = document.createElement('div'); lineLayer.style.position = 'absolute'; lineLayer.style.inset = '0'; lineLayer.style.pointerEvents = 'none'; lineLayer.style.zIndex = '0'; canvas.appendChild(lineLayer); const lines = []; for (let i = 0; i < 40; i++) { const l = document.createElement('div'); l.className = 'nanobot-line'; l.style.background = 'rgba(0,212,255,0.15)'; l.style.height = '1px'; lineLayer.appendChild(l); lines.push(l); } const statusEl = canvas.querySelector('.dream-status'); const updateStatus = () => { if (!statusEl) return; api('/api/stats').then(data => { const c = data.chimera || {}; const t = data.training || {}; statusEl.innerHTML = 'Swarm: ' + bots.length + ' activeOrgans: ' + (c.organs || '—') + ' · ' + (c.accuracy ? (c.accuracy * 100).toFixed(0) + '%' : '—') + 'Loss: ' + (t.loss || '—') + ''; }); }; let frame = 0; const animate = () => { frame++; bots.forEach(b => { b.x += b.vx + Math.sin(frame * 0.01 + b.y) * 0.02; b.y += b.vy + Math.cos(frame * 0.01 + b.x) * 0.02; if (b.x < 0 || b.x > 94) { b.vx *= -1; b.x = Math.max(0, Math.min(94, b.x)); } if (b.y < 0 || b.y > 94) { b.vy *= -1; b.y = Math.max(0, Math.min(94, b.y)); } b.el.style.left = b.x + '%'; b.el.style.top = b.y + '%'; }); const connections = []; for (let i = 0; i < bots.length; i++) { for (let j = i + 1; j < bots.length; j++) { const dx = bots[i].x - bots[j].x; const dy = bots[i].y - bots[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 12) connections.push({ i, j, dist }); } } const w = canvas.offsetWidth; const h = canvas.offsetHeight; lines.forEach((l, idx) => { if (idx < connections.length) { const c = connections[idx]; const b1 = bots[c.i]; const b2 = bots[c.j]; const x1 = b1.x / 100 * w; const y1 = b1.y / 100 * h; const x2 = b2.x / 100 * w; const y2 = b2.y / 100 * h; const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); const angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI; l.style.left = x1 + 'px'; l.style.top = y1 + 'px'; l.style.width = len + 'px'; l.style.transform = 'rotate(' + angle + 'deg)'; l.style.opacity = Math.max(0, (1 - c.dist / 12) * 0.6); } else { l.style.opacity = '0'; } }); trails.forEach((t, idx) => { const bot = bots[idx % bots.length]; t.style.left = (bot.x + (Math.random() - 0.5) * 5) + '%'; t.style.top = (bot.y + (Math.random() - 0.5) * 5) + '%'; }); if (frame % 30 === 0) updateStatus(); requestAnimationFrame(animate); }; animate(); } // ── Mesh: Node Identity & Sharing ── const meshNodeId = document.getElementById('mesh-node-id'); const meshPubkey = document.getElementById('mesh-pubkey'); const meshUptime = document.getElementById('mesh-uptime'); const meshPeers = document.getElementById('mesh-peers'); const meshSharedCount = document.getElementById('mesh-shared-count'); const meshFeed = document.getElementById('mesh-feed'); const meshInsights = document.getElementById('mesh-insights-content'); const meshPublishBtn = document.getElementById('mesh-publish-btn'); const meshToggleGlobal = document.getElementById('mesh-toggle-global'); const meshToggleBench = document.getElementById('mesh-toggle-benchmarks'); const meshToggleDreams = document.getElementById('mesh-toggle-dreams'); const meshToggleStats = document.getElementById('mesh-toggle-stats'); async function pollMesh() { const identity = await api('/api/mesh/identity'); if (identity.node_id && meshNodeId) { meshNodeId.textContent = identity.node_id; meshPubkey.textContent = identity.pubkey_fingerprint || '—'; const up = identity.uptime || 0; const h = Math.floor(up / 3600); const m = Math.floor((up % 3600) / 60); const s = up % 60; meshUptime.textContent = h + 'h ' + m + 'm ' + s + 's'; meshPeers.textContent = identity.peers || 0; } const discover = await api('/api/mesh/discover'); if (discover.items && meshFeed) { if (discover.items.length === 0) { meshFeed.innerHTML = '
🕸 No peers yet — shared content from other nodes appears here when they connect.
'; } else { meshFeed.innerHTML = discover.items.map(item => { const ts = item.timestamp ? new Date(item.timestamp * 1000).toLocaleString() : ''; return '
' + '
' + '' + escapeHtml(item.name) + '' + '' + (item.node_id ? item.node_id.slice(0, 8) + '..' : '—') + '' + '
' + '
' + escapeHtml(item.type || 'project') + '' + ts + '
' + '
'; }).join(''); } } const shareData = await api('/api/mesh/discover?local=true'); if (shareData.items && meshSharedCount) { meshSharedCount.textContent = shareData.items.length + ' items shared'; } } function escapeHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // Publish current project to mesh if (meshPublishBtn && editor) { meshPublishBtn.addEventListener('click', async () => { const code = editor.value; const desc = (code.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'))[0] || 'untitled').replace(/^def\s+|^class\s+|^#\s*/g, '').trim().slice(0, 60) || 'untitled'; meshPublishBtn.textContent = '📤 Publishing...'; meshPublishBtn.disabled = true; await api('/api/mesh/share', { name: desc, content: code, type: 'project', share_benchmarks: meshToggleBench ? meshToggleBench.checked : true, share_dreams: meshToggleDreams ? meshToggleDreams.checked : false, share_stats: meshToggleStats ? meshToggleStats.checked : false, }); meshPublishBtn.textContent = '✓ Published'; setTimeout(() => { meshPublishBtn.textContent = '📤 Publish Current Project'; meshPublishBtn.disabled = false; }, 2000); pollMesh(); }); } // Chimera cross-node insights — triggered when editor content changes let insightTimer = null; if (editor && meshInsights) { editor.addEventListener('input', () => { clearTimeout(insightTimer); insightTimer = setTimeout(async () => { const code = editor.value.trim(); if (code.length < 30) return; const result = await api('/api/mesh/related', { text: code.slice(0, 500) }); if (result.related && result.related.length > 0) { meshInsights.innerHTML = result.related.map(r => '
' + '
💭 ' + escapeHtml(r.name) + '
' + '
' + escapeHtml(r.description || 'Related public project') + ' · node ' + (r.node_id ? r.node_id.slice(0, 8) + '..' : '—') + '
' + '
' ).join(''); } else { meshInsights.innerHTML = '
💭 Working on something? Chimera will search the mesh for related public work.
'; } }, 1500); }); } setInterval(pollMesh, 5000); // ── Device: APK Builder ── const deviceSdk = document.getElementById('device-sdk'); const deviceAdb = document.getElementById('device-adb'); const deviceGradle = document.getElementById('device-gradle'); const deviceDevices = document.getElementById('device-devices'); const deviceOutput = document.getElementById('device-output'); const deviceApkStatus = document.getElementById('device-apk-status'); const deviceVerifyOutput = document.getElementById('device-verify-output'); const deviceGenBtn = document.getElementById('device-gen-btn'); const deviceBuildBtn = document.getElementById('device-build-btn'); const deviceVerifyBtn = document.getElementById('device-verify-btn'); const deviceInstallBtn = document.getElementById('device-install-btn'); let currentApk = null; function deviceWrite(text, cls = '') { if (!deviceOutput) return; if (deviceOutput.querySelector('.device-output-empty')) { deviceOutput.innerHTML = ''; } const d = document.createElement('div'); d.className = 'device-output-line ' + cls; d.textContent = text; deviceOutput.appendChild(d); deviceOutput.scrollTop = deviceOutput.scrollHeight; } async function pollDevice() { const env = await api('/api/android/env'); if (deviceSdk) { deviceSdk.textContent = env.sdk ? 'installed' : 'not found'; deviceSdk.className = 'device-value' + (env.sdk ? ' good' : ' bad'); } if (deviceAdb) { deviceAdb.textContent = env.adb ? 'available' : 'not found'; deviceAdb.className = 'device-value' + (env.adb ? ' good' : ' bad'); } if (deviceGradle) { deviceGradle.textContent = env.gradle || 'not found'; deviceGradle.className = 'device-value' + (env.gradle ? ' good' : ' bad'); } if (deviceDevices) { deviceDevices.textContent = (env.devices || 0) + ' connected'; deviceDevices.className = 'device-value' + (env.devices > 0 ? ' good' : ''); } } if (deviceGenBtn) { deviceGenBtn.addEventListener('click', async () => { const name = document.getElementById('device-app-name').value || 'FSIApp'; const pkg = document.getElementById('device-package').value || 'com.fsi.app'; const activity = document.getElementById('device-activity').value || 'MainActivity'; deviceWrite('Generating project: ' + name + '...', 'info'); deviceGenBtn.disabled = true; const result = await api('/api/android/generate', { name, package: pkg, activity }); if (result.success) { deviceWrite('Project generated: ' + result.path, 'success'); } else { deviceWrite(result.error || 'Generation failed', 'error'); } deviceGenBtn.disabled = false; }); } if (deviceBuildBtn) { deviceBuildBtn.addEventListener('click', async () => { const name = document.getElementById('device-app-name').value || 'FSIApp'; const pkg = document.getElementById('device-package').value || 'com.fsi.app'; const activity = document.getElementById('device-activity').value || 'MainActivity'; deviceWrite('Building APK: ' + name + '...', 'info'); deviceWrite('First build takes 3-5 min (downloads deps)...', 'info'); deviceBuildBtn.disabled = true; deviceBuildBtn.textContent = '⚙️ Building...'; const result = await api('/api/android/build', { name, package: pkg, activity }); if (result.success && result.apk) { currentApk = result.apk; deviceWrite('APK built: ' + result.apk, 'success'); deviceWrite('Size: ' + (result.size_mb || '?') + ' MB', 'info'); deviceApkStatus.textContent = 'APK ready: ' + result.apk; deviceVerifyBtn.disabled = false; deviceInstallBtn.disabled = false; } else { deviceWrite(result.error || 'Build failed', 'error'); } deviceBuildBtn.disabled = false; deviceBuildBtn.textContent = '🔥 Build APK'; }); } if (deviceVerifyBtn) { deviceVerifyBtn.addEventListener('click', async () => { deviceVerifyBtn.disabled = true; deviceVerifyOutput.textContent = 'Verifying...'; const result = await api('/api/android/verify'); if (result.valid) { deviceVerifyOutput.innerHTML = '✓ Valid APK\n' + 'Package: ' + (result.package || '—') + '\n' + 'Version: ' + (result.version_name || '—') + ' (' + (result.version_code || '—') + ')\n' + 'Min SDK: ' + (result.min_sdk || '—') + '\n' + 'Target SDK: ' + (result.target_sdk || '—') + '\n' + 'Activity: ' + (result.activity || '—') + '\n' + 'Size: ' + (result.size ? (result.size / 1024 / 1024).toFixed(2) + ' MB' : '—'); } else { deviceVerifyOutput.textContent = '✗ ' + (result.error || 'Verification failed'); } deviceVerifyBtn.disabled = false; }); } if (deviceInstallBtn) { deviceInstallBtn.addEventListener('click', async () => { deviceInstallBtn.disabled = true; deviceInstallBtn.textContent = '⏳ Installing...'; const result = await api('/api/android/install'); if (result.success) { deviceVerifyOutput.textContent = '✓ Installed successfully'; } else { deviceVerifyOutput.textContent = '✗ ' + (result.error || 'Install failed'); } deviceInstallBtn.disabled = false; deviceInstallBtn.textContent = '📤 Install'; }); } setInterval(pollDevice, 10000); // ── Initial vault load ── loadVaultTree(); });