// ═══════════════════════════════════════════════════════════ // MNEMOSYNE — Memory Inspect Panel (issue #1227) // ═══════════════════════════════════════════════════════════ // // Side-panel detail view for memory crystals. // Opens when a crystal is clicked; auto-closes on empty-space click. // // Usage from app.js: // MemoryInspect.init({ onNavigate: fn }); // MemoryInspect.show(memData, regionDef); // MemoryInspect.hide(); // MemoryInspect.isOpen(); // ═══════════════════════════════════════════════════════════ const MemoryInspect = (() => { let _panel = null; let _onNavigate = null; // callback(memId) — navigate to a linked memory // ─── INIT ──────────────────────────────────────────────── function init(opts = {}) { _onNavigate = opts.onNavigate || null; _panel = document.getElementById('memory-inspect-panel'); if (!_panel) { console.warn('[MemoryInspect] Panel element #memory-inspect-panel not found in DOM'); } } // ─── SHOW ──────────────────────────────────────────────── function show(data, regionDef) { if (!_panel) return; const region = regionDef || {}; const colorHex = region.color ? '#' + region.color.toString(16).padStart(6, '0') : '#4af0c0'; const strength = data.strength != null ? data.strength : 0.7; const vitality = Math.round(Math.max(0, Math.min(1, strength)) * 100); let vitalityColor = '#4af0c0'; if (vitality < 30) vitalityColor = '#ff4466'; else if (vitality < 60) vitalityColor = '#ffaa22'; const ts = data.timestamp ? new Date(data.timestamp) : null; const created = ts && !isNaN(ts) ? ts.toLocaleString() : '—'; // Linked memories let linksHtml = ''; if (data.connections && data.connections.length > 0) { linksHtml = data.connections .map(id => ``) .join(''); } else { linksHtml = 'No linked memories'; } _panel.innerHTML = `
${region.glyph || '\u25C8'}
${_esc(_truncate(data.id || '\u2014', 28))}
${_esc(region.label || data.category || '\u2014')}
${_esc(data.content || '(empty)')}
${vitality}%
Source ${_esc(data.source || '\u2014')}
Created ${created}
`; // Wire close button const closeBtn = _panel.querySelector('#mi-close-btn'); if (closeBtn) closeBtn.addEventListener('click', hide); // Wire copy button const copyBtn = _panel.querySelector('#mi-copy-btn'); if (copyBtn) { copyBtn.addEventListener('click', () => { const text = data.content || ''; if (navigator.clipboard) { navigator.clipboard.writeText(text).then(() => { copyBtn.textContent = '\u2713 Copied'; setTimeout(() => { copyBtn.textContent = '\u2398 Copy'; }, 1500); }).catch(() => _fallbackCopy(text)); } else { _fallbackCopy(text); } }); } // Wire link navigation const linksContainer = _panel.querySelector('#mi-links'); if (linksContainer) { linksContainer.addEventListener('click', (e) => { const btn = e.target.closest('.mi-link-btn'); if (btn && _onNavigate) _onNavigate(btn.dataset.memid); }); } _panel.style.display = 'flex'; // Trigger CSS animation requestAnimationFrame(() => _panel.classList.add('mi-visible')); } // ─── HIDE ───────────────────────────────────────────────── function hide() { if (!_panel) return; _panel.classList.remove('mi-visible'); // Wait for CSS transition before hiding const onEnd = () => { _panel.style.display = 'none'; _panel.removeEventListener('transitionend', onEnd); }; _panel.addEventListener('transitionend', onEnd); // Safety fallback if transition doesn't fire setTimeout(() => { if (_panel) _panel.style.display = 'none'; }, 350); } // ─── QUERY ──────────────────────────────────────────────── function isOpen() { return _panel != null && _panel.style.display !== 'none'; } // ─── HELPERS ────────────────────────────────────────────── function _esc(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function _truncate(str, n) { return str.length > n ? str.slice(0, n - 1) + '\u2026' : str; } function _fallbackCopy(text) { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } return { init, show, hide, isOpen }; })(); export { MemoryInspect };