refactor: address 5-pass audit findings for antipatterns, bottlenecks, memory efficiency, and LLM handlers
This commit is contained in:
1 parent
626403900f
commit
924b6d09fa
30 files changed
+1120
-503
No files matched your search
+187
-104
@@ -1,5 +1,5 @@
|
||||
// server/src/dashboard.ts
|
||||
var currentTabId = "graph-tab";
|
||||
var currentTabId = "activity-tab";
|
||||
function switchTab(tabId, btn) {
|
||||
currentTabId = tabId;
|
||||
document.querySelectorAll(".tab-content").forEach((el) => el.classList.remove("active"));
|
||||
@@ -141,6 +141,7 @@ function renderFilters() {
|
||||
</button>`;
|
||||
}).join("");
|
||||
}
|
||||
var lastGraphJson = "";
|
||||
function updateGraphData() {
|
||||
const newNodes = [];
|
||||
const newEdges = [];
|
||||
@@ -160,10 +161,10 @@ Type: ${entity.entity_type}`,
|
||||
});
|
||||
nodeIds.add(name);
|
||||
}
|
||||
rawRelations.forEach((r) => {
|
||||
rawRelations.forEach((r, idx) => {
|
||||
if (nodeIds.has(r.from) && nodeIds.has(r.to)) {
|
||||
newEdges.push({
|
||||
id: r.from + "-" + r.to + "-" + r.relation_type,
|
||||
id: `${r.from}-${r.to}-${r.relation_type}-${idx}`,
|
||||
from: r.from,
|
||||
to: r.to,
|
||||
label: r.relation_type,
|
||||
@@ -172,19 +173,23 @@ Type: ${entity.entity_type}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
const currentNodes = nodesData.getIds();
|
||||
nodesData.remove(currentNodes.filter((id) => !nodeIds.has(String(id))));
|
||||
nodesData.update(newNodes);
|
||||
const currentEdges = edgesData.getIds();
|
||||
const newEdgeIds = new Set(newEdges.map((e) => e.id));
|
||||
edgesData.remove(currentEdges.filter((id) => !newEdgeIds.has(String(id))));
|
||||
edgesData.update(newEdges);
|
||||
nodesData.clear();
|
||||
if (newNodes.length > 0)
|
||||
nodesData.add(newNodes);
|
||||
edgesData.clear();
|
||||
if (newEdges.length > 0)
|
||||
edgesData.add(newEdges);
|
||||
renderFilters();
|
||||
}
|
||||
async function loadGraph() {
|
||||
try {
|
||||
const res = await fetch("/api/graph");
|
||||
const data = await res.json();
|
||||
const dataText = await res.text();
|
||||
if (dataText === lastGraphJson && network) {
|
||||
return;
|
||||
}
|
||||
lastGraphJson = dataText;
|
||||
const data = JSON.parse(dataText);
|
||||
rawEntities = data.entities || {};
|
||||
rawRelations = data.relations || [];
|
||||
updateGraphData();
|
||||
@@ -195,10 +200,24 @@ async function loadGraph() {
|
||||
const options = {
|
||||
nodes: { shape: "dot", size: 16, font: { size: 12 } },
|
||||
edges: { color: { inherit: "from", opacity: 0.6 }, font: { size: 10, align: "middle" }, smooth: { type: "continuous" } },
|
||||
physics: { barnesHut: { gravitationalConstant: -2000, centralGravity: 0.3, springLength: 95 } },
|
||||
physics: {
|
||||
enabled: true,
|
||||
solver: "barnesHut",
|
||||
barnesHut: { gravitationalConstant: -3000, centralGravity: 0.3, springLength: 95, springConstant: 0.04, damping: 0.09, avoidOverlap: 0.1 },
|
||||
stabilization: { enabled: true, iterations: 150, updateInterval: 25, fit: true }
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 100, zoomView: false }
|
||||
};
|
||||
network = new vis.Network(container, { nodes: nodesData, edges: edgesData }, options);
|
||||
network.once("stabilizationIterationsDone", function() {
|
||||
network.setOptions({ physics: { enabled: false } });
|
||||
});
|
||||
network.on("dragStart", function() {
|
||||
network.setOptions({ physics: { enabled: true } });
|
||||
});
|
||||
network.on("dragEnd", function() {
|
||||
network.setOptions({ physics: { enabled: false } });
|
||||
});
|
||||
container.addEventListener("wheel", function(event) {
|
||||
event.preventDefault();
|
||||
const direction = event.deltaY > 0 ? -0.15 : 0.15;
|
||||
@@ -468,41 +487,157 @@ async function loadActivityHistory() {
|
||||
console.error("Failed to load activity history", e);
|
||||
}
|
||||
}
|
||||
var wsConnected = false;
|
||||
var activeSSE = null;
|
||||
var tabRefreshDebounceTimers = {};
|
||||
function requestDomainRefresh(domain) {
|
||||
const tabMap = {
|
||||
graph: "graph-tab",
|
||||
task: "task-tab",
|
||||
sticky: "sticky-tab",
|
||||
techdebt: "techdebt-tab",
|
||||
snippets: "snippets-tab",
|
||||
terminal: "terminal-tab",
|
||||
memos: "memos-tab",
|
||||
workspaces: "workspaces-tab",
|
||||
pinned: "pinned-tab",
|
||||
pr: "pr-tab",
|
||||
preferences: "preferences-tab"
|
||||
};
|
||||
const targetTab = tabMap[domain];
|
||||
if (!targetTab || targetTab !== currentTabId) {
|
||||
return;
|
||||
}
|
||||
if (tabRefreshDebounceTimers[domain]) {
|
||||
clearTimeout(tabRefreshDebounceTimers[domain]);
|
||||
}
|
||||
tabRefreshDebounceTimers[domain] = setTimeout(() => {
|
||||
delete tabRefreshDebounceTimers[domain];
|
||||
switch (domain) {
|
||||
case "graph":
|
||||
loadGraph();
|
||||
break;
|
||||
case "task":
|
||||
loadTasks();
|
||||
break;
|
||||
case "sticky":
|
||||
loadStickyNotes();
|
||||
break;
|
||||
case "techdebt":
|
||||
loadTechDebt();
|
||||
break;
|
||||
case "snippets":
|
||||
loadSnippets();
|
||||
break;
|
||||
case "terminal":
|
||||
loadTerminal();
|
||||
break;
|
||||
case "memos":
|
||||
loadMemos();
|
||||
break;
|
||||
case "workspaces":
|
||||
loadWorkspaces();
|
||||
break;
|
||||
case "pinned":
|
||||
loadPinned();
|
||||
break;
|
||||
case "pr":
|
||||
loadPRs();
|
||||
break;
|
||||
case "preferences":
|
||||
loadPreferences();
|
||||
break;
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
function handleIncomingActivity(payload) {
|
||||
let category = "";
|
||||
let method = "";
|
||||
if (typeof payload === "object" && payload !== null) {
|
||||
method = payload.method || "";
|
||||
if (payload.params) {
|
||||
category = (payload.params.category || payload.params.type || "").toUpperCase();
|
||||
} else {
|
||||
category = (payload.category || payload.type || "").toUpperCase();
|
||||
}
|
||||
}
|
||||
if (method === "notifications/resources/updated" || category === "GRAPH" || category === "DECISION") {
|
||||
requestDomainRefresh("graph");
|
||||
} else if (method === "notifications/task/completed" || category.startsWith("TASK")) {
|
||||
requestDomainRefresh("task");
|
||||
} else if (category === "STICKY_NOTE" || category === "STICKY") {
|
||||
requestDomainRefresh("sticky");
|
||||
} else if (category === "TECH_DEBT" || category === "ERROR_FIX" || category === "CHECKPOINT") {
|
||||
requestDomainRefresh("techdebt");
|
||||
} else if (category === "SNIPPET") {
|
||||
requestDomainRefresh("snippets");
|
||||
} else if (category === "TERMINAL") {
|
||||
requestDomainRefresh("terminal");
|
||||
} else if (category === "MEMO" || category === "MILESTONE" || category === "HANDOFF_MEMO") {
|
||||
requestDomainRefresh("memos");
|
||||
} else if (category === "WORKSPACE" || category === "CONTEXT_WORKSPACE") {
|
||||
requestDomainRefresh("workspaces");
|
||||
} else if (category === "PINNED" || category === "PINNED_FILE") {
|
||||
requestDomainRefresh("pinned");
|
||||
} else if (category === "PR" || category === "PR_CHECKLIST") {
|
||||
requestDomainRefresh("pr");
|
||||
} else if (category === "PREFERENCE" || category === "PREFERENCES") {
|
||||
requestDomainRefresh("preferences");
|
||||
}
|
||||
const feed = document.getElementById("activity-feed");
|
||||
if (feed) {
|
||||
if (feed.querySelector(".feed-entry") === null) {
|
||||
feed.innerHTML = "";
|
||||
}
|
||||
const isScrolledToTop = feed.scrollTop <= 20;
|
||||
const div = document.createElement("div");
|
||||
div.className = "feed-entry";
|
||||
div.innerHTML = parseActivityPayload(payload);
|
||||
feed.prepend(div);
|
||||
while (feed.children.length > MAX_ACTIVITY_HISTORY && feed.lastChild) {
|
||||
feed.removeChild(feed.lastChild);
|
||||
}
|
||||
if (isScrolledToTop)
|
||||
feed.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
function setupWS() {
|
||||
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const ws = new WebSocket(`${protocol}//${location.host}/ws?client=ui`);
|
||||
let ws;
|
||||
try {
|
||||
ws = new WebSocket(`${protocol}//${location.host}/ws?client=ui`);
|
||||
} catch (e) {
|
||||
console.error("WebSocket creation error", e);
|
||||
setupSSE();
|
||||
return;
|
||||
}
|
||||
ws.onopen = function() {
|
||||
wsConnected = true;
|
||||
if (activeSSE) {
|
||||
activeSSE.close();
|
||||
activeSSE = null;
|
||||
}
|
||||
};
|
||||
ws.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === "activity" || data.method === "notifications/activity" || data.method === "notifications/task/completed" || data.method === "notifications/resources/updated") {
|
||||
if (currentTabId !== "activity-tab") {
|
||||
refreshActiveTab();
|
||||
}
|
||||
const feed = document.getElementById("activity-feed");
|
||||
if (feed) {
|
||||
if (feed.querySelector(".feed-entry") === null) {
|
||||
feed.innerHTML = "";
|
||||
}
|
||||
const isScrolledToTop = feed.scrollTop <= 20;
|
||||
const div = document.createElement("div");
|
||||
div.className = "feed-entry";
|
||||
const payload = data.params || data.data || data;
|
||||
div.innerHTML = parseActivityPayload(payload);
|
||||
feed.prepend(div);
|
||||
while (feed.children.length > MAX_ACTIVITY_HISTORY && feed.lastChild) {
|
||||
feed.removeChild(feed.lastChild);
|
||||
}
|
||||
if (isScrolledToTop)
|
||||
feed.scrollTop = 0;
|
||||
}
|
||||
const payload = data.params || data.data || data;
|
||||
handleIncomingActivity(payload);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("WebSocket message parse error", e);
|
||||
}
|
||||
};
|
||||
ws.onclose = function() {
|
||||
console.log("WebSocket closed, attempting to reconnect in 3s...");
|
||||
setTimeout(setupWS, 3000);
|
||||
wsConnected = false;
|
||||
console.log("WebSocket closed, falling back to SSE and retrying WS in 5s...");
|
||||
setupSSE();
|
||||
setTimeout(setupWS, 5000);
|
||||
};
|
||||
ws.onerror = function() {
|
||||
wsConnected = false;
|
||||
setupSSE();
|
||||
};
|
||||
}
|
||||
async function loadPreferences() {
|
||||
@@ -663,31 +798,24 @@ async function loadVersion() {
|
||||
}
|
||||
}
|
||||
function setupSSE() {
|
||||
if (wsConnected || activeSSE)
|
||||
return;
|
||||
try {
|
||||
const sse = new EventSource("/api/activity/stream");
|
||||
sse.onmessage = function(event) {
|
||||
if (event.data) {
|
||||
activeSSE = new EventSource("/api/activity/stream");
|
||||
activeSSE.onmessage = function(event) {
|
||||
if (event.data && !wsConnected) {
|
||||
try {
|
||||
if (currentTabId !== "activity-tab") {
|
||||
refreshActiveTab();
|
||||
}
|
||||
const feed = document.getElementById("activity-feed");
|
||||
if (feed) {
|
||||
if (feed.querySelector(".feed-entry") === null) {
|
||||
feed.innerHTML = "";
|
||||
}
|
||||
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.lastChild) {
|
||||
feed.removeChild(feed.lastChild);
|
||||
}
|
||||
if (isScrolledToTop)
|
||||
feed.scrollTop = 0;
|
||||
}
|
||||
} catch (e) {}
|
||||
const parsed = JSON.parse(event.data);
|
||||
handleIncomingActivity(parsed);
|
||||
} catch (e) {
|
||||
handleIncomingActivity(event.data);
|
||||
}
|
||||
}
|
||||
};
|
||||
activeSSE.onerror = function() {
|
||||
if (activeSSE) {
|
||||
activeSSE.close();
|
||||
activeSSE = null;
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
@@ -697,7 +825,7 @@ function setupSSE() {
|
||||
document.addEventListener("keydown", function(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
||||
e.preventDefault();
|
||||
const searchTabBtn = document.querySelectorAll(".tab-button")[1];
|
||||
const searchTabBtn = document.querySelector('button[onclick*="search-tab"]');
|
||||
if (searchTabBtn) {
|
||||
switchTab("search-tab", searchTabBtn);
|
||||
}
|
||||
@@ -708,53 +836,8 @@ document.addEventListener("keydown", function(e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
function refreshActiveTab() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
loadVersion();
|
||||
loadGraph();
|
||||
loadActivityHistory();
|
||||
setupWS();
|
||||
setupSSE();
|
||||
var observer = new MutationObserver(() => updateGraphData());
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
||||
Reference in new issue
Block a user