refactor: consolidate nvim crates, extract server library, and update workspace dependencies

This commit is contained in:
Riz Ashraf committed 2026-10-04 01:42:59 +01:00
1 parent a083719cf1
commit 533adfd41b
53 files changed
+5967 -1230

No files matched your search

+226 -54
View File
@@ -633,29 +633,61 @@
<script>
// --- Tabs ---
let currentTabId = 'graph-tab';
function switchTab(tabId, btn) {
currentTabId = tabId;
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.tab-button').forEach(el => el.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
btn.classList.add('active');
if (btn) btn.classList.add('active');
if (tabId === 'graph-tab' && network) {
// Resize graph when becoming visible again
network.redraw();
network.fit();
loadGraph();
} else if (tabId === 'activity-tab') {
const feed = document.getElementById('activity-feed');
feed.scrollTop = 0;
} else if (tabId === 'task-tab') {
loadTasks();
} else if (tabId === 'preferences-tab') {
loadPreferences();
} else if (tabId === 'sticky-tab') {
loadStickyNotes();
} else {
loadAllExtras();
switch (tabId) {
case 'graph-tab':
if (network) {
network.redraw();
} else {
loadGraph();
}
break;
case 'activity-tab':
loadActivityHistory();
break;
case 'task-tab':
loadTasks();
break;
case 'preferences-tab':
loadPreferences();
break;
case 'sticky-tab':
loadStickyNotes();
break;
case 'techdebt-tab':
loadTechDebt();
break;
case 'adrs-tab':
loadADRs();
break;
case 'workspaces-tab':
loadWorkspaces();
break;
case 'pinned-tab':
loadPinned();
break;
case 'memos-tab':
loadMemos();
break;
case 'snippets-tab':
loadSnippets();
break;
case 'pr-tab':
loadPRs();
break;
case 'terminal-tab':
loadTerminal();
break;
}
}
@@ -1028,23 +1060,99 @@
// --- WebSocket Activity Feed ---
const MAX_ACTIVITY_HISTORY = 100;
function parseActivityPayload(item) {
let category = 'activity';
let message = '';
let timestamp = Date.now();
if (typeof item === 'string') {
try {
const parsed = JSON.parse(item);
return parseActivityPayload(parsed);
} catch (e) {
message = item;
}
} else if (typeof item === 'object' && item !== null) {
if (item.method === 'notifications/activity' && item.params) {
message = item.params.message || JSON.stringify(item.params);
timestamp = item.params.time || item.params.timestamp || timestamp;
category = item.params.category || item.params.type || 'tool';
} else {
category = item.category || item.type || category;
message = item.summary || item.message || item.description || item.data || JSON.stringify(item);
timestamp = item.timestamp || item.time || item.updated_at || timestamp;
if (typeof timestamp === 'number' && timestamp < 10000000000) {
timestamp = timestamp * 1000;
}
}
}
// Fallback inference from tool name if category is generic
const lowerCategory = category.toLowerCase();
if (lowerCategory === 'activity' || lowerCategory === 'mcp_tool' || lowerCategory === 'tool') {
const match = message.match(/Agent executed tool:\s*([a-z0-9_]+)/i);
if (match && match[1]) {
const tool = match[1].toLowerCase();
if (tool.includes('clipboard')) category = 'CLIPBOARD';
else if (tool.includes('graph') || tool.includes('entities') || tool.includes('relations') || tool.includes('schema')) category = 'GRAPH';
else if (tool.includes('decision')) category = 'DECISION';
else if (tool.includes('code_change') || tool.includes('ast')) category = 'CODE';
else if (tool.includes('task') || tool.includes('milestone')) category = 'TASK';
else if (tool.includes('note')) category = 'STICKY_NOTE';
else if (tool.includes('checkpoint') || tool.includes('snapshot')) category = 'CHECKPOINT';
else if (tool.includes('subagent') || tool.includes('namespace')) category = 'SUBAGENT';
else if (tool.includes('snippet')) category = 'SNIPPET';
else if (tool.includes('error')) category = 'ERROR_FIX';
else category = tool.toUpperCase();
}
}
const catUpper = category.toUpperCase();
const colorMap = {
'GRAPH': '#2ecc71',
'DECISION': '#f39c12',
'CODE': '#3498db',
'TASK': '#1abc9c',
'CLIPBOARD': '#9b59b6',
'STICKY_NOTE': '#e67e22',
'CHECKPOINT': '#e74c3c',
'ERROR_FIX': '#e74c3c',
'TECH_DEBT': '#d35400',
'SUBAGENT': '#8e44ad',
'SNIPPET': '#16a085',
'TOOL': '#3498db',
'SYSTEM': '#95a5a6'
};
const badgeColor = colorMap[catUpper] || '#3498db';
const dateObj = new Date(timestamp);
const validDate = isNaN(dateObj.getTime()) ? new Date() : dateObj;
const timeStr = validDate.toLocaleString([], {
month: '2-digit', day: '2-digit', hour: '2-digit', minute:'2-digit', second:'2-digit'
});
return `<span class="time">[${timeStr}]</span> <span style="background:var(--canvas-bg); color:${badgeColor}; padding:2px 6px; border-radius:3px; font-weight:bold; margin-right:6px; border:1px solid ${badgeColor}60; text-transform:uppercase; font-size:0.75em;">${catUpper}</span> ${message}`;
}
async function loadActivityHistory() {
try {
const response = await fetch('/api/activity');
const history = await response.json();
const feed = document.getElementById('activity-feed');
if (!feed) return;
feed.innerHTML = '';
[...history].reverse().forEach(item => {
const div = document.createElement('div');
div.className = 'feed-entry';
const timeStr = new Date(item.time).toLocaleString([], {month: '2-digit', day: '2-digit', hour: '2-digit', minute:'2-digit', second:'2-digit'});
div.innerHTML = `<span class="time">[${timeStr}]</span> ${item.message || item.data}`;
div.innerHTML = parseActivityPayload(item);
feed.appendChild(div);
});
if (history.length > 0) {
feed.scrollTop = 0;
}
} catch(e) {}
} catch(e) {
console.error("Failed to load activity history", e);
}
}
function setupWS() {
@@ -1056,27 +1164,21 @@
try {
const data = JSON.parse(event.data);
if (data.type === 'activity') {
// Check scroll state before appending
const isScrolledToTop = feed.scrollTop <= 20;
const isScrolledToTop = feed ? feed.scrollTop <= 20 : true;
const div = document.createElement('div');
div.className = 'feed-entry';
const timeStr = new Date(data.data.time).toLocaleString([], {month: '2-digit', day: '2-digit', hour: '2-digit', minute:'2-digit', second:'2-digit'});
div.innerHTML = `<span class="time">[${timeStr}]</span> ${data.data.message || data.data.data || data.data}`;
feed.prepend(div);
// Enforce max history
while (feed.children.length > MAX_ACTIVITY_HISTORY) {
feed.removeChild(feed.lastChild);
}
// Auto-scroll logic
if (isScrolledToTop) {
feed.scrollTop = 0;
div.innerHTML = parseActivityPayload(data.data);
if (feed) {
feed.prepend(div);
while (feed.children.length > MAX_ACTIVITY_HISTORY) {
feed.removeChild(feed.lastChild);
}
if (isScrolledToTop) feed.scrollTop = 0;
}
}
} catch (e) {
// Ignore non-JSON or other messages for now
console.error("WebSocket message parse error", e);
}
};
@@ -1161,7 +1263,7 @@
}
}
function loadAllExtras() {
function loadTerminal() {
loadGenericList('/api/terminal/history', 'terminal-container', item => `
<span style="font-weight:bold; font-size:1.05em; color:var(--text-primary);">${item.command}</span>
<span style="margin-left:15px; font-size:0.85em;"><span style="font-weight:bold; color:var(--text-secondary);">Exit Code:</span> <span style="color:${item.exit_code === 0 ? 'var(--success-color)' : 'var(--error-color)'}; font-weight:bold;">${item.exit_code}</span></span>
@@ -1170,7 +1272,9 @@
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">CWD: ${item.cwd || 'Unknown'}</span>
</div>
`);
}
function loadTechDebt() {
loadGenericList('/api/tech_debts', 'techdebt-container', item => `
<strong>${item.id}</strong> ${item.is_resolved ? '<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); margin-left: 8px;">(Resolved)</span>' : '<span style="color:var(--open-color); font-weight:bold; background: rgba(230, 126, 34, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--open-color); margin-left: 8px;">(Open)</span>'}
<div style="margin-top:5px;"><strong>Description:</strong> ${item.description}</div>
@@ -1186,7 +1290,9 @@
<div style="font-size:0.9em; margin-bottom:8px; margin-top:5px;"><em>Solution:</em> ${item.solution || ''}</div>
<div style="font-family:monospace; font-size:0.85em; color:#e74c3c;">Commit: ${item.git_commit || 'None'}</div>
`);
}
function loadADRs() {
loadGenericList('/api/adrs', 'adrs-container', item => `
<strong>${item.id} | ${item.title}</strong>
<span style="float:right; background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color); ${item.status === 'superseded' ? 'text-decoration: line-through; color:var(--error-color);' : 'color:var(--success-color);'}">${item.status}</span>
@@ -1195,24 +1301,44 @@
<div style="font-size:0.9em; margin-bottom:8px;"><em>Consequence:</em> ${item.consequence || ''}</div>
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${item.supersedes}</div>` : ''}
`);
}
function loadWorkspaces() {
loadGenericList('/api/context_workspaces', 'workspaces-container', item => `
<strong>${item.name}</strong>
<div style="margin-bottom:8px;">${item.description || ''}</div>
<div style="font-family:monospace; font-size:0.85em; color:#3498db;">${(item.paths || []).join(', ')}</div>
`);
}
function loadPinned() {
loadGenericList('/api/pinned_files', 'pinned-container', item => `
<strong>${item.path || item.file_path || item.id}</strong>
<div style="margin-top:5px; font-size:0.85em; color:var(--text-secondary);">${item.reason || item.description || 'Pinned'}</div>
`);
}
function loadMemos() {
loadGenericList('/api/handoff_memos', 'memos-container', item => `
<strong>Memo from ${item.author || 'System'}</strong>
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4;">${item.content || item.summary || ''}</div>
`);
loadGenericList('/api/milestones', 'milestones-container', item => `
<strong>${item.title || item.name}</strong>
<div style="margin-top:5px; font-size:0.85em;">${item.description || ''}</div>
`);
}
function loadSnippets() {
loadGenericList('/api/snippets', 'snippets-container', item => `
<strong>${item.description || 'Snippet'}</strong>
<div style="margin-bottom:8px; font-size:0.8em;">Language: ${item.language || 'txt'} | Tags: ${(item.tags || []).join(', ')}</div>
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em;">${item.content || item.code || ''}</pre>
`);
}
function loadPRs() {
loadGenericList('/api/pr_checklists', 'pr-container', item => `
<strong>${item.name || 'Checklist'}</strong>
<ul style="padding-left:20px; font-size:0.9em;">
@@ -1225,6 +1351,17 @@
`);
}
function loadAllExtras() {
loadTerminal();
loadTechDebt();
loadADRs();
loadWorkspaces();
loadPinned();
loadMemos();
loadSnippets();
loadPRs();
}
async function testClipboard() {
const modal = document.getElementById('clipboard-modal');
const resultDiv = document.getElementById('clipboard-result');
@@ -1264,16 +1401,17 @@
sse.onmessage = function(event) {
if (event.data) {
try {
const rec = JSON.parse(event.data);
const feed = document.getElementById('activity-feed');
if (feed) {
const item = document.createElement('div');
item.style.padding = '8px 12px';
item.style.borderBottom = '1px solid var(--border-color)';
item.style.fontSize = '0.85em';
item.style.background = 'rgba(39, 174, 96, 0.08)';
item.innerHTML = `<strong>${rec.category || 'activity'}</strong>: ${rec.summary || event.data}`;
feed.insertBefore(item, feed.firstChild);
const isScrolledToTop = feed.scrollTop <= 20;
const div = document.createElement('div');
div.className = 'feed-entry';
div.innerHTML = parseActivityPayload(event.data);
feed.prepend(div);
while (feed.children.length > MAX_ACTIVITY_HISTORY) {
feed.removeChild(feed.lastChild);
}
if (isScrolledToTop) feed.scrollTop = 0;
}
} catch(e) {}
}
@@ -1300,10 +1438,6 @@
// --- Start ---
loadVersion();
loadGraph();
loadTasks();
loadStickyNotes();
loadActivityHistory();
loadAllExtras();
setupWS();
setupSSE();
@@ -1311,12 +1445,50 @@
const observer = new MutationObserver(() => updateGraphData());
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
// Smart background polling: refresh only the currently active tab
setInterval(() => {
loadGraph();
loadTasks();
loadStickyNotes();
loadAllExtras();
}, 5000);
switch (currentTabId) {
case 'graph-tab':
loadGraph();
break;
case 'task-tab':
loadTasks();
break;
case 'sticky-tab':
loadStickyNotes();
break;
case 'techdebt-tab':
loadTechDebt();
break;
case 'adrs-tab':
loadADRs();
break;
case 'workspaces-tab':
loadWorkspaces();
break;
case 'pinned-tab':
loadPinned();
break;
case 'memos-tab':
loadMemos();
break;
case 'snippets-tab':
loadSnippets();
break;
case 'pr-tab':
loadPRs();
break;
case 'terminal-tab':
loadTerminal();
break;
case 'preferences-tab':
loadPreferences();
break;
case 'activity-tab':
loadActivityHistory();
break;
}
}, 10000);
</script>
</body>
</html>