// ===== State ===== let state = { connections: [], currentConn: null, status: {}, kernel: null, settings: {}, meta: {}, pluginMeta: {}, settingsPlugins: ['core'], selectedSection: 'core', messages: [], chatLoading: false, chatStage: '', installedPlugins: [], pluginInfo: null, startedAt: null, uptimeTick: null, sidebarRefreshTick: null, eventSource: null, _chatHistoryLoaded: false, _starmapData: null, }; // ===== Markdown Renderer (lightweight, no dependencies) ===== function renderMarkdown(t) { if (!t) return ''; let s = String(t) .replace(/&/g,'&').replace(//g,'>'); // code blocks (fenced) s = s.replace(/```(\w*)\n([\s\S]*?)```/g, '
$2
'); // inline code s = s.replace(/`([^`]+)`/g, '$1'); // headers s = s.replace(/^### (.+)$/gm, '

$1

'); s = s.replace(/^## (.+)$/gm, '

$1

'); s = s.replace(/^# (.+)$/gm, '

$1

'); // bold & italic s = s.replace(/\*\*\*(.+?)\*\*\*/g, '$1'); s = s.replace(/\*\*(.+?)\*\*/g, '$1'); s = s.replace(/\*(.+?)\*/g, '$1'); // links s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); // images s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); // blockquote s = s.replace(/^> (.+)$/gm, '
$1
'); // horizontal rule s = s.replace(/^---$/gm, '
'); // unordered list s = s.replace(/^[\s]*[-*] (.+)$/gm, '
  • $1
  • '); s = s.replace(/(
  • .*<\/li>\n?)+/g, ''); // ordered list s = s.replace(/^[\s]*\d+\. (.+)$/gm, '
  • $1
  • '); // paragraphs: double newlines s = s.replace(/\n\n/g, '

    '); s = '

    ' + s + '

    '; // clean nested ps from lists s = s.replace(/<\/p>\n?