feat(dashboard,adrs): enrich dashboard tabs with domain model parity and resolve consequence schema handling
This commit is contained in:
1 parent
73c3d41c87
commit
f546579596
5 files changed
+798
-143
No files matched your search
+307
-77
@@ -110,21 +110,36 @@ function showInspector(nodeId) {
|
||||
const titleEl = document.getElementById("inspector-title");
|
||||
if (titleEl)
|
||||
titleEl.innerText = entity.name;
|
||||
const typeEl = document.getElementById("inspector-type");
|
||||
if (typeEl)
|
||||
typeEl.innerText = entity.entity_type;
|
||||
const nsEl = document.getElementById("inspector-namespace");
|
||||
if (nsEl)
|
||||
nsEl.innerText = entity.namespace || "global";
|
||||
const obsHtml = (entity.observations || []).map((o) => `<li>${o}</li>`).join("");
|
||||
const metaEl = document.querySelector(".inspector-meta");
|
||||
if (metaEl) {
|
||||
const createdStr = entity.created_at ? new Date(entity.created_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
const updatedStr = entity.updated_at ? new Date(entity.updated_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
metaEl.innerHTML = `
|
||||
<div><strong>Type:</strong> <span id="inspector-type">${escapeHtml(entity.entity_type)}</span></div>
|
||||
<div><strong>Namespace:</strong> <span id="inspector-namespace">${escapeHtml(entity.namespace || "global")}</span></div>
|
||||
${entity.repo_name ? `<div><strong>Repository:</strong> <span style="font-family:monospace;">${escapeHtml(entity.repo_name)}</span></div>` : ""}
|
||||
${entity.file_path ? `<div><strong>File:</strong> <span style="font-family:monospace; word-break:break-all;">${escapeHtml(entity.file_path)}</span></div>` : ""}
|
||||
${entity.git_branch ? `<div><strong>Branch:</strong> <span style="font-family:monospace;">${escapeHtml(entity.git_branch)}</span></div>` : ""}
|
||||
${createdStr ? `<div><strong>Created:</strong> <span>${escapeHtml(createdStr)}</span></div>` : ""}
|
||||
${updatedStr ? `<div><strong>Updated:</strong> <span>${escapeHtml(updatedStr)}</span></div>` : ""}
|
||||
`;
|
||||
} else {
|
||||
const typeEl = document.getElementById("inspector-type");
|
||||
if (typeEl)
|
||||
typeEl.innerText = entity.entity_type;
|
||||
const nsEl = document.getElementById("inspector-namespace");
|
||||
if (nsEl)
|
||||
nsEl.innerText = entity.namespace || "global";
|
||||
}
|
||||
const obsHtml = (entity.observations || []).map((o) => `<li>${escapeHtml(o)}</li>`).join("");
|
||||
const obsEl = document.getElementById("inspector-observations");
|
||||
if (obsEl)
|
||||
obsEl.innerHTML = obsHtml || "<li><em>No observations recorded.</em></li>";
|
||||
const rels = rawRelations.filter((r) => r.from === nodeId || r.to === nodeId);
|
||||
const relHtml = rels.map((r) => {
|
||||
if (r.from === nodeId)
|
||||
return `<li>→ <em>${r.relation_type}</em> → <strong>${r.to}</strong></li>`;
|
||||
return `<li>← <em>${r.relation_type}</em> ← <strong>${r.from}</strong></li>`;
|
||||
return `<li>→ <em>${escapeHtml(r.relation_type)}</em> → <strong>${escapeHtml(r.to)}</strong></li>`;
|
||||
return `<li>← <em>${escapeHtml(r.relation_type)}</em> ← <strong>${escapeHtml(r.from)}</strong></li>`;
|
||||
}).join("");
|
||||
const relEl = document.getElementById("inspector-relations");
|
||||
if (relEl)
|
||||
@@ -142,10 +157,10 @@ function toggleFilter(type) {
|
||||
updateGraphData();
|
||||
}
|
||||
function renderFilters() {
|
||||
const container = document.getElementById("graph-filters");
|
||||
if (!container)
|
||||
const container2 = document.getElementById("graph-filters");
|
||||
if (!container2)
|
||||
return;
|
||||
container.innerHTML = Array.from(allTypes).sort().map((type) => {
|
||||
container2.innerHTML = Array.from(allTypes).sort().map((type) => {
|
||||
const isActive = activeFilters.has(type);
|
||||
const color = getColorForType(type).bg || getColorForType(type).background;
|
||||
return `<button class="filter-chip ${isActive ? "active" : ""}" onclick="toggleFilter('${type}')">
|
||||
@@ -212,8 +227,8 @@ async function loadGraph() {
|
||||
rawRelations = data.relations || [];
|
||||
updateGraphData();
|
||||
if (!network) {
|
||||
const container = document.getElementById("network-container");
|
||||
if (!container)
|
||||
const container2 = document.getElementById("network-container");
|
||||
if (!container2)
|
||||
return;
|
||||
const options = {
|
||||
nodes: { shape: "dot", size: 16, font: { size: 12 } },
|
||||
@@ -242,7 +257,7 @@ async function loadGraph() {
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 100, zoomView: false }
|
||||
};
|
||||
network = new vis.Network(container, { nodes: nodesData, edges: edgesData }, options);
|
||||
network = new vis.Network(container2, { nodes: nodesData, edges: edgesData }, options);
|
||||
network.once("stabilizationIterationsDone", function() {
|
||||
network.setOptions({ physics: { enabled: false } });
|
||||
});
|
||||
@@ -252,7 +267,7 @@ async function loadGraph() {
|
||||
network.on("dragEnd", function() {
|
||||
network.setOptions({ physics: { enabled: false } });
|
||||
});
|
||||
container.addEventListener("wheel", function(event) {
|
||||
container2.addEventListener("wheel", function(event) {
|
||||
event.preventDefault();
|
||||
const direction = event.deltaY > 0 ? -0.15 : 0.15;
|
||||
zoomGraph(direction);
|
||||
@@ -268,9 +283,9 @@ async function loadGraph() {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load graph", err);
|
||||
const container = document.getElementById("network-container");
|
||||
if (container) {
|
||||
container.innerHTML = '<div style="color:#e74c3c;padding:20px;font-weight:bold;">Error loading graph: ' + err.message + "</div>";
|
||||
const container2 = document.getElementById("network-container");
|
||||
if (container2) {
|
||||
container2.innerHTML = '<div style="color:#e74c3c;padding:20px;font-weight:bold;">Error loading graph: ' + err.message + "</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,20 +340,46 @@ function buildTaskTreeHTML(tasks, parentId, depth = 0) {
|
||||
isBlocked = true;
|
||||
}
|
||||
}
|
||||
const prio = (t.priority || "").toLowerCase();
|
||||
let prioBadge = "";
|
||||
if (prio === "critical" || prio === "urgent") {
|
||||
prioBadge = `<span style="background:rgba(231,76,60,0.18); color:#e74c3c; border:1px solid rgba(231,76,60,0.5); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">CRITICAL</span>`;
|
||||
} else if (prio === "high") {
|
||||
prioBadge = `<span style="background:rgba(230,126,34,0.18); color:#e67e22; border:1px solid rgba(230,126,34,0.5); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">HIGH</span>`;
|
||||
} else if (prio === "medium" || prio === "med") {
|
||||
prioBadge = `<span style="background:rgba(52,152,219,0.15); color:#3498db; border:1px solid rgba(52,152,219,0.4); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">MEDIUM</span>`;
|
||||
} else if (prio === "low") {
|
||||
prioBadge = `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.7em; padding:2px 6px; border-radius:4px; text-transform:uppercase;">LOW</span>`;
|
||||
}
|
||||
html += `<div class="${cardClass}" style="margin-left: ${depth * 20}px; margin-bottom: 10px;">`;
|
||||
html += `<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px; margin-bottom:4px;">`;
|
||||
html += `<div style="display:flex; align-items:center; gap:6px; flex-wrap:wrap;">`;
|
||||
if (prioBadge)
|
||||
html += prioBadge;
|
||||
if (t.assigned_agent) {
|
||||
html += `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.7em; padding:2px 6px; border-radius:4px;">\uD83E\uDD16 ${escapeHtml(t.assigned_agent)}</span>`;
|
||||
}
|
||||
if (isBlocked && !isCompleted && !isCancelled) {
|
||||
html += `<div style="background:#e74c3c; color:white; font-size:0.75em; padding:2px 6px; border-radius:3px; display:inline-block; margin-bottom:6px; font-weight:bold;">[BLOCKED]</div>`;
|
||||
if (blockers.length > 0) {
|
||||
html += `<div style="font-size:0.8em; color:#e74c3c; margin-bottom:6px;">Waiting on: ${blockers.join(", ")}</div>`;
|
||||
}
|
||||
html += `<span style="background:#e74c3c; color:white; font-size:0.7em; padding:2px 6px; border-radius:3px; font-weight:bold;">BLOCKED</span>`;
|
||||
}
|
||||
if (isCancelled) {
|
||||
html += `<div style="background:#95a5a6; color:white; font-size:0.75em; padding:2px 6px; border-radius:3px; display:inline-block; margin-bottom:6px; font-weight:bold;">[CANCELLED]</div>`;
|
||||
html += `<span style="background:#95a5a6; color:white; font-size:0.7em; padding:2px 6px; border-radius:3px; font-weight:bold;">CANCELLED</span>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
if (!isCompleted && !isCancelled && !isBlocked) {
|
||||
html += `<button class="complete-btn" style="position:static;" onclick="completeTask('${escapeHtml(t.id)}')" title="Mark Completed">✓</button>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
if (isBlocked && blockers.length > 0 && !isCompleted && !isCancelled) {
|
||||
html += `<div style="font-size:0.8em; color:#e74c3c; margin-bottom:6px;">Waiting on: ${escapeHtml(blockers.join(", "))}</div>`;
|
||||
}
|
||||
html += `<strong>${escapeHtml(t.title)}</strong>`;
|
||||
if (t.description) {
|
||||
html += `<div style="margin-top:5px; font-size:0.9em; color:var(--text-secondary); line-height:1.4;">${escapeHtml(t.description)}</div>`;
|
||||
}
|
||||
if (t.verification_command) {
|
||||
html += `<div style="margin-top:6px; font-size:0.8em; font-family:monospace; background:var(--canvas-bg); padding:3px 8px; border-radius:4px; border:1px solid var(--border-color); color:var(--accent-color);">\uD83E\uDDEA Test: ${escapeHtml(t.verification_command)}</div>`;
|
||||
}
|
||||
const criteria = t.acceptance_criteria || [];
|
||||
if (criteria.length > 0) {
|
||||
html += `<ul style="margin:8px 0 0 0; padding-left:20px; font-size: 0.9em; color: var(--text-secondary);">`;
|
||||
@@ -349,16 +390,21 @@ function buildTaskTreeHTML(tasks, parentId, depth = 0) {
|
||||
unmetCriteria = true;
|
||||
const check = isMet ? "☑" : "☐";
|
||||
const strike = isMet ? "text-decoration: line-through;" : "";
|
||||
html += `<li style="${strike}">${check} ${c.description}</li>`;
|
||||
html += `<li style="${strike}">${check} ${escapeHtml(c.description)}${c.verification_command ? ` <code style="font-size:0.8em; background:var(--canvas-bg); padding:1px 4px; border-radius:3px;">(${escapeHtml(c.verification_command)})</code>` : ""}</li>`;
|
||||
});
|
||||
html += `</ul>`;
|
||||
if (unmetCriteria && !isCompleted && !isCancelled)
|
||||
isBlocked = true;
|
||||
}
|
||||
html += progressHtml;
|
||||
if (!isCompleted && !isCancelled && !isBlocked) {
|
||||
html += `<button class="complete-btn" onclick="completeTask('${t.id}')" title="Mark Completed">✓</button>`;
|
||||
if (t.git_branch || t.repo_name) {
|
||||
html += `<div style="margin-top:8px; font-size:0.75em; display:flex; gap:6px; flex-wrap:wrap; color:var(--text-secondary);">`;
|
||||
if (t.repo_name)
|
||||
html += `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(t.repo_name)}</span>`;
|
||||
if (t.git_branch)
|
||||
html += `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(t.git_branch)}</span>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
html += progressHtml;
|
||||
if (allChildren.length > 0) {
|
||||
html += `<div style="margin-top: 15px; border-left: 2px solid var(--border-color); padding-left: 10px;">`;
|
||||
html += buildTaskTreeHTML(tasks, t.id, 0);
|
||||
@@ -416,14 +462,14 @@ async function handleSearch(e) {
|
||||
signal: activeSearchAbortController.signal
|
||||
});
|
||||
const data = await res.json();
|
||||
const container = document.getElementById("search-results");
|
||||
if (!container)
|
||||
const container2 = document.getElementById("search-results");
|
||||
if (!container2)
|
||||
return;
|
||||
if (!data.results || data.results.length === 0) {
|
||||
container.innerHTML = '<div style="padding:15px;color:var(--text-secondary);">No results found.</div>';
|
||||
container2.innerHTML = '<div style="padding:15px;color:var(--text-secondary);">No results found.</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = data.results.map((r) => `
|
||||
container2.innerHTML = data.results.map((r) => `
|
||||
<div class="search-result-item" onclick="switchTab('graph-tab'); if(rawEntities['${r.id}']){ showInspector('${r.id}'); if(network){ network.selectNodes(['${r.id}']); network.focus('${r.id}', {scale:1.2, animation:true}); } }" style="cursor:pointer;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
|
||||
<div>
|
||||
@@ -730,23 +776,23 @@ function setupWS() {
|
||||
};
|
||||
}
|
||||
async function loadGenericList(endpoint, containerId, formatter) {
|
||||
const container = document.getElementById(containerId);
|
||||
try {
|
||||
const res = await fetch(endpoint);
|
||||
if (!res.ok) {
|
||||
if (container) {
|
||||
container.innerHTML = `<div style="color:var(--error-color); padding:20px; text-align:center;">⚠️ Failed to load ${endpoint} (${res.status} ${res.statusText})</div>`;
|
||||
if (container2) {
|
||||
container2.innerHTML = `<div style="color:var(--error-color); padding:20px; text-align:center;">⚠️ Failed to load ${endpoint} (${res.status} ${res.statusText})</div>`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!container)
|
||||
const container2 = document.getElementById(containerId);
|
||||
if (!container2)
|
||||
return;
|
||||
if (!data || data.length === 0) {
|
||||
container.innerHTML = '<div style="color:var(--text-secondary); padding:20px; text-align:center;">No items recorded.</div>';
|
||||
container2.innerHTML = '<div style="color:var(--text-secondary); padding:20px; text-align:center;">No items recorded.</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = data.map((item) => {
|
||||
container2.innerHTML = data.map((item) => {
|
||||
const date = item.timestamp ? new Date(item.timestamp * 1000).toLocaleString() : "";
|
||||
return `<div class="task-card" style="border-left-color: #9b59b6;">
|
||||
<div style="font-size:0.75em; color:var(--text-secondary); float:right;">${date}</div>
|
||||
@@ -805,46 +851,172 @@ function loadLedger() {
|
||||
});
|
||||
}
|
||||
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>
|
||||
<div style="margin-top:10px; font-size:0.85em;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; margin-right:8px; border:1px solid var(--border-color);">OS: ${item.os || "Unknown"}</span>
|
||||
<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>
|
||||
loadGenericList("/api/terminal/history", "terminal-container", (item) => {
|
||||
const isSuccess = item.exit_code === 0;
|
||||
const statusColor = isSuccess ? "var(--success-color)" : "var(--error-color)";
|
||||
const statusBg = isSuccess ? "rgba(46, 204, 113, 0.12)" : "rgba(231, 76, 60, 0.12)";
|
||||
const durationStr = item.duration_ms != null ? item.duration_ms < 1000 ? `${item.duration_ms}ms` : `${(item.duration_ms / 1000).toFixed(2)}s` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:10px;">
|
||||
<span style="font-family:monospace; font-weight:bold; font-size:1.05em; color:var(--text-primary); word-break:break-all;">$ ${escapeHtml(item.command)}</span>
|
||||
<div style="display:flex; gap:6px; align-items:center; flex-shrink:0;">
|
||||
${durationStr ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.75em; border:1px solid var(--border-color); color:var(--text-secondary); font-family:monospace;">⏱️ ${escapeHtml(durationStr)}</span>` : ""}
|
||||
<span style="background:${statusBg}; color:${statusColor}; border:1px solid ${statusColor}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; font-family:monospace;">Exit ${item.exit_code}</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
<div style="margin-top:8px; font-size:0.85em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.shell ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); font-weight:600;">\uD83D\uDC1A ${escapeHtml(item.shell)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">OS: ${escapeHtml(item.os || "Unknown")}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color); word-break:break-all;">\uD83D\uDCC2 ${escapeHtml(item.cwd || "Unknown")}</span>
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
${item.invoker ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">\uD83D\uDC64 ${escapeHtml(item.invoker)}</span>` : ""}
|
||||
</div>
|
||||
${item.status_reason ? `<div style="margin-top:6px; font-size:0.85em; color:var(--error-color);"><strong>Reason:</strong> ${escapeHtml(item.status_reason)}</div>` : ""}
|
||||
${item.stdout_summary ? `<div style="margin-top:6px; font-size:0.85em; color:var(--text-secondary);"><strong>Output:</strong> ${escapeHtml(item.stdout_summary)}</div>` : ""}
|
||||
${item.error_output ? `
|
||||
<details style="margin-top:8px;" ${!isSuccess ? "open" : ""}>
|
||||
<summary style="font-size:0.8em; color:var(--error-color); cursor:pointer; font-weight:600;">Error Output</summary>
|
||||
<pre style="background:#1a0f0f; color:#ff6b6b; padding:8px; border-radius:4px; border:1px solid rgba(231,76,60,0.3); font-size:0.8em; overflow-x:auto; margin-top:4px; white-space:pre-wrap;">${escapeHtml(item.error_output)}</pre>
|
||||
</details>
|
||||
` : ""}
|
||||
`;
|
||||
});
|
||||
}
|
||||
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>
|
||||
<div style="margin-top:5px;"><strong>Ideal Solution:</strong> ${item.ideal_solution}</div>
|
||||
<div style="margin-top:10px;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; margin-right:8px; border:1px solid var(--border-color);">Git Commit: ${item.git_commit || "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color);">Branch: ${item.git_branch || "None"}</span>
|
||||
loadGenericList("/api/tech_debts", "techdebt-container", (item) => {
|
||||
const sev = (item.severity || "medium").toLowerCase();
|
||||
let sevBg = "rgba(241, 196, 15, 0.15)";
|
||||
let sevColor = "#f1c40f";
|
||||
let sevBorder = "rgba(241, 196, 15, 0.4)";
|
||||
if (sev === "critical" || sev === "blocker") {
|
||||
sevBg = "rgba(231, 76, 60, 0.18)";
|
||||
sevColor = "#e74c3c";
|
||||
sevBorder = "rgba(231, 76, 60, 0.5)";
|
||||
} else if (sev === "high") {
|
||||
sevBg = "rgba(230, 126, 34, 0.18)";
|
||||
sevColor = "#e67e22";
|
||||
sevBorder = "rgba(230, 126, 34, 0.5)";
|
||||
} else if (sev === "low" || sev === "trivial") {
|
||||
sevBg = "rgba(52, 152, 219, 0.15)";
|
||||
sevColor = "#3498db";
|
||||
sevBorder = "rgba(52, 152, 219, 0.4)";
|
||||
}
|
||||
const statusBadge = item.is_resolved ? '<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color);">✓ Resolved</span>' : '<span style="color:var(--open-color); font-weight:bold; background: rgba(230, 126, 34, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--open-color);">● Open</span>';
|
||||
const fileLoc = item.file_path ? `${item.file_path}${item.line_range ? `:${item.line_range}` : ""}` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap;">
|
||||
<strong style="font-family:monospace; font-size:1em;">${escapeHtml(item.id)}</strong>
|
||||
<span style="background:${sevBg}; color:${sevColor}; border:1px solid ${sevBorder}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">${escapeHtml(item.severity || "medium")}</span>
|
||||
${item.effort_estimate ? `<span style="background:var(--canvas-bg); font-size:0.75em; padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">⏳ ${escapeHtml(item.effort_estimate)}</span>` : ""}
|
||||
</div>
|
||||
<div>${statusBadge}</div>
|
||||
</div>
|
||||
`);
|
||||
${fileLoc ? `<div style="margin-top:6px; font-family:monospace; font-size:0.85em; color:var(--accent-color);">\uD83D\uDCC4 ${escapeHtml(fileLoc)}</div>` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4;"><strong>Description:</strong> ${escapeHtml(item.description || "")}</div>
|
||||
${item.workaround ? `<div style="margin-top:4px; font-size:0.85em; color:var(--text-secondary); background:var(--canvas-bg); padding:4px 8px; border-radius:4px; border-left:3px solid #f39c12;"><strong>Workaround:</strong> ${escapeHtml(item.workaround)}</div>` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4; color:var(--text-secondary);"><strong>Ideal Solution:</strong> ${escapeHtml(item.ideal_solution || "")}</div>
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">Commit: ${item.git_commit ? escapeHtml(item.git_commit.substring(0, 8)) : "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(item.git_branch || "None")}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">\uD83C\uDFF7️ ${escapeHtml(item.namespace || "global")}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
loadGenericList("/api/error_fixes", "errorfixes-container", (item) => `
|
||||
<strong>${item.signature || "Error"}</strong> <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>
|
||||
<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>
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px;">
|
||||
<div>
|
||||
<strong style="color:var(--text-primary); font-family:monospace; font-size:1em;">${escapeHtml(item.signature || "Error")}</strong>
|
||||
${item.error_category ? `<span style="background:rgba(231,76,60,0.15); color:var(--error-color); border:1px solid rgba(231,76,60,0.4); font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; margin-left:6px;">${escapeHtml(item.error_category)}</span>` : ""}
|
||||
${item.toolchain ? `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.75em; padding:2px 6px; border-radius:4px; margin-left:6px; font-family:monospace;">\uD83D\uDD27 ${escapeHtml(item.toolchain)}</span>` : ""}
|
||||
</div>
|
||||
<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); flex-shrink:0;">✓ Resolved</span>
|
||||
</div>
|
||||
<div style="margin-top:8px; font-size:0.9em; line-height:1.4;"><strong>Solution:</strong> ${escapeHtml(item.solution || "")}</div>
|
||||
${item.stack_trace ? `
|
||||
<details style="margin-top:8px;">
|
||||
<summary style="font-size:0.8em; color:var(--text-secondary); cursor:pointer; font-weight:600;">Stack Trace</summary>
|
||||
<pre style="background:#111417; color:#ff7b72; padding:8px; border-radius:4px; border:1px solid var(--border-color); font-size:0.8em; overflow-x:auto; margin-top:4px; white-space:pre-wrap;">${escapeHtml(item.stack_trace)}</pre>
|
||||
</details>
|
||||
` : ""}
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">Commit: ${item.git_commit ? escapeHtml(item.git_commit.substring(0, 8)) : "None"}</span>
|
||||
${item.git_branch ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(item.git_branch)}</span>` : ""}
|
||||
</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>
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:8px;"><em>Context:</em> ${item.context || ""}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${item.decision || ""}</div>
|
||||
<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>` : ""}
|
||||
`);
|
||||
loadGenericList("/api/adrs", "adrs-container", (item) => {
|
||||
const statusLower = (item.status || "").toLowerCase();
|
||||
let badgeStyle = "color:var(--accent-color); border-color:rgba(88, 166, 255, 0.4); background:rgba(88, 166, 255, 0.12);";
|
||||
let badgeText = `● ${escapeHtml(item.status || "accepted")}`;
|
||||
if (statusLower === "implemented" || statusLower === "resolved") {
|
||||
badgeStyle = "color:var(--success-color); border-color:rgba(46, 160, 67, 0.4); background:rgba(46, 160, 67, 0.15); font-weight:600;";
|
||||
badgeText = `✓ ${escapeHtml(item.status)}`;
|
||||
} else if (statusLower === "superseded") {
|
||||
badgeStyle = "text-decoration: line-through; color:var(--error-color); border-color:rgba(248, 81, 73, 0.4); background:rgba(248, 81, 73, 0.12);";
|
||||
badgeText = `✕ ${escapeHtml(item.status)}`;
|
||||
} else if (statusLower === "proposed" || statusLower === "draft") {
|
||||
badgeStyle = "color:var(--warning-color); border-color:rgba(210, 153, 34, 0.4); background:rgba(210, 153, 34, 0.12);";
|
||||
badgeText = `○ ${escapeHtml(item.status)}`;
|
||||
}
|
||||
const resolvedDate = item.resolved_at ? new Date(item.resolved_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<strong>${escapeHtml(item.id)} | ${escapeHtml(item.title)}</strong>
|
||||
<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color); ${badgeStyle}">${badgeText}</span>
|
||||
</div>
|
||||
${item.git_commit || item.git_branch || item.task_id || item.resolved_at ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:4px; font-family:monospace; display:flex; gap:12px; flex-wrap:wrap;">
|
||||
${item.git_branch || item.git_commit ? `<span>\uD83D\uDCE6 ${escapeHtml(item.git_branch || "repo")} @ ${escapeHtml((item.git_commit || "").substring(0, 8))}</span>` : ""}
|
||||
${item.task_id ? `<span>\uD83C\uDFAF Task: ${escapeHtml(item.task_id)}</span>` : ""}
|
||||
${resolvedDate ? `<span>✓ Resolved: ${escapeHtml(resolvedDate)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:8px;"><em>Context:</em> ${escapeHtml(item.context || "")}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${escapeHtml(item.decision || "")}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Consequence:</em> ${escapeHtml(item.consequence || "")}</div>
|
||||
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${escapeHtml(item.supersedes)}</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/handoff_memos", "memos-container", (item) => {
|
||||
const blockers = Array.isArray(item.blockers) && item.blockers.length > 0 ? item.blockers : [];
|
||||
const actionItems = Array.isArray(item.action_items) && item.action_items.length > 0 ? item.action_items : [];
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<strong style="color:var(--text-primary); font-size:1em;">\uD83D\uDC64 Memo from ${escapeHtml(item.author || "System")}</strong>
|
||||
<span style="font-size:0.75em; color:var(--text-secondary); background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">${escapeHtml(item.namespace || "global")}</span>
|
||||
</div>
|
||||
${item.git_branch || item.vcs_revision || item.session_id ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:3px; font-family:monospace; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
${item.git_branch ? `<span>\uD83C\uDF3F ${escapeHtml(item.git_branch)}</span>` : ""}
|
||||
${item.vcs_revision ? `<span>Rev: ${escapeHtml(item.vcs_revision.substring(0, 8))}</span>` : ""}
|
||||
${item.session_id ? `<span>Session: ${escapeHtml(item.session_id)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4; margin-top:8px;">${escapeHtml(item.content || item.summary || "")}</div>
|
||||
${blockers.length > 0 ? `
|
||||
<div style="margin-top:8px; background:rgba(231,76,60,0.1); border:1px solid rgba(231,76,60,0.3); border-radius:4px; padding:6px 10px;">
|
||||
<div style="color:var(--error-color); font-weight:bold; font-size:0.85em;">\uD83D\uDEAB Blockers:</div>
|
||||
<ul style="margin:4px 0 0 16px; padding:0; font-size:0.85em; color:var(--text-primary);">
|
||||
${blockers.map((b) => `<li>${escapeHtml(b)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
${actionItems.length > 0 ? `
|
||||
<div style="margin-top:8px; background:rgba(52,152,219,0.1); border:1px solid rgba(52,152,219,0.3); border-radius:4px; padding:6px 10px;">
|
||||
<div style="color:var(--accent-color); font-weight:bold; font-size:0.85em;">\uD83D\uDCCB Action Items:</div>
|
||||
<ul style="margin:4px 0 0 16px; padding:0; font-size:0.85em; color:var(--text-primary);">
|
||||
${actionItems.map((a) => `<li>${escapeHtml(a)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
`;
|
||||
});
|
||||
loadGenericList("/api/session_summaries", "summaries-container", (item) => `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<strong>Session ${item.session_id ? escapeHtml(item.session_id) : "Summary"}</strong>
|
||||
@@ -855,17 +1027,75 @@ function loadMemos() {
|
||||
${item.tasks_completed && item.tasks_completed.length > 0 ? `<div style="margin-top:8px; font-size:0.85em;"><strong>Tasks Completed:</strong><ul style="margin:4px 0 0 16px; padding:0;">${item.tasks_completed.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul></div>` : ""}
|
||||
${item.next_steps && item.next_steps.length > 0 ? `<div style="margin-top:6px; font-size:0.85em;"><strong>Next Steps:</strong><ul style="margin:4px 0 0 16px; padding:0;">${item.next_steps.map((s) => `<li>${escapeHtml(s)}</li>`).join("")}</ul></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>
|
||||
`);
|
||||
loadGenericList("/api/milestones", "milestones-container", (item) => {
|
||||
const statusLower = (item.status || "pending").toLowerCase();
|
||||
let statusBg = "rgba(52, 152, 219, 0.15)";
|
||||
let statusColor = "var(--accent-color)";
|
||||
let statusLabel = item.status || "Pending";
|
||||
if (statusLower === "completed" || statusLower === "done") {
|
||||
statusBg = "rgba(46, 204, 113, 0.15)";
|
||||
statusColor = "var(--success-color)";
|
||||
statusLabel = "✓ Completed";
|
||||
} else if (statusLower === "in_progress" || statusLower === "active") {
|
||||
statusBg = "rgba(230, 126, 34, 0.15)";
|
||||
statusColor = "var(--open-color)";
|
||||
statusLabel = "● In Progress";
|
||||
}
|
||||
const targetDate = item.target_date ? new Date(item.target_date * 1000).toLocaleDateString() : "";
|
||||
const completedDate = item.completed_at ? new Date(item.completed_at * 1000).toLocaleDateString() : "";
|
||||
const deliverables = Array.isArray(item.deliverables) && item.deliverables.length > 0 ? item.deliverables : [];
|
||||
const taskIds = Array.isArray(item.task_ids) && item.task_ids.length > 0 ? item.task_ids : [];
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<strong style="font-size:1.05em; color:var(--text-primary);">${escapeHtml(item.title || item.name || "Milestone")}</strong>
|
||||
<span style="background:${statusBg}; color:${statusColor}; border:1px solid ${statusColor}; font-size:0.75em; font-weight:bold; padding:2px 8px; border-radius:4px;">${escapeHtml(statusLabel)}</span>
|
||||
</div>
|
||||
${targetDate || completedDate ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:3px; display:flex; gap:10px;">
|
||||
${targetDate ? `<span>\uD83C\uDFAF Target: ${escapeHtml(targetDate)}</span>` : ""}
|
||||
${completedDate ? `<span>✓ Finished: ${escapeHtml(completedDate)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4; color:var(--text-primary);">${escapeHtml(item.description || "")}</div>
|
||||
${deliverables.length > 0 ? `
|
||||
<div style="margin-top:8px; font-size:0.85em;">
|
||||
<strong>Deliverables:</strong>
|
||||
<ul style="margin:4px 0 0 16px; padding:0;">
|
||||
${deliverables.map((d) => `<li>${escapeHtml(d)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
${taskIds.length > 0 ? `
|
||||
<div style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
<span style="font-size:0.8em; color:var(--text-secondary);">Linked Tasks:</span>
|
||||
${taskIds.map((tid) => `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; font-family:monospace; border:1px solid var(--border-color);">${escapeHtml(tid)}</span>`).join("")}
|
||||
</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>
|
||||
`);
|
||||
loadGenericList("/api/snippets", "snippets-container", (item) => {
|
||||
const code = item.code || item.content || "";
|
||||
const tags = Array.isArray(item.tags) ? item.tags : [];
|
||||
const origin = item.origin_file ? `${item.origin_file}${item.line_range ? `:${item.line_range}` : ""}` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px;">
|
||||
<div>
|
||||
<strong style="font-size:1.05em; color:var(--text-primary);">${escapeHtml(item.name || item.description || "Snippet")}</strong>
|
||||
${item.language ? `<span style="background:var(--canvas-bg); color:var(--accent-color); border:1px solid var(--border-color); font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; margin-left:6px; font-family:monospace;">${escapeHtml(item.language)}</span>` : ""}
|
||||
</div>
|
||||
<button class="complete-btn" style="position:static;" onclick='navigator.clipboard.writeText(${JSON.stringify(code)}).then(() => { this.innerText="✓ Copied"; setTimeout(() => { this.innerText="\uD83D\uDCCB Copy"; }, 1500); })' title="Copy to Clipboard">\uD83D\uDCCB Copy</button>
|
||||
</div>
|
||||
${item.description && item.description !== item.name ? `<div style="margin-top:4px; font-size:0.85em; color:var(--text-secondary);">${escapeHtml(item.description)}</div>` : ""}
|
||||
${origin ? `<div style="margin-top:6px; font-family:monospace; font-size:0.8em; color:var(--accent-color);">\uD83D\uDCC4 ${escapeHtml(origin)}</div>` : ""}
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em; margin-top:8px; border:1px solid var(--border-color);">${escapeHtml(code)}</pre>
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
${tags.map((t) => `<span style="background:var(--card-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; border:1px solid var(--border-color); color:var(--text-secondary);">#${escapeHtml(t)}</span>`).join(" ")}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
function loadAllExtras() {
|
||||
loadTerminal();
|
||||
|
||||
Reference in new issue
Block a user