ClauseGuard / extension /sidepanel.js
gaurv007's picture
fix(sidepanel): add CRITICAL severity filter/cards, description, proper severity ordering
dd28267 verified
/**
* ClauseGuard — Side Panel v4.3
*
* FIXED v4.3: Added CRITICAL severity support (filter, cards, icons, descriptions).
* FIXED v4.3: Severity ordering now uses numeric mapping consistently.
*/
const DESCS = {
"Limitation of liability": "Company limits or excludes liability for losses or damages.",
"Unilateral termination": "They can close your account without reason.",
"Unilateral change": "Terms can change without your consent.",
"Content removal": "Your content can be deleted without notice.",
"Contract by using": "You agree just by visiting or using the site.",
"Choice of law": "Foreign law applies instead of your local protections.",
"Jurisdiction": "Disputes handled in their preferred court.",
"Arbitration": "You waive your right to sue in court.",
};
// Severity numeric ordering (higher = more severe)
const SEV_ORDER = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1 };
// SVG icons for severity
const SEV_ICONS = {
CRITICAL: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
HIGH: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
MEDIUM: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>',
LOW: '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
};
let allClauses = [];
let currentFilter = "all";
async function loadResults() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) return;
const results = await chrome.runtime.sendMessage({ type: "GET_RESULTS", tabId: tab.id });
if (!results || !results.results) {
document.getElementById("empty-state").style.display = "block";
return;
}
document.getElementById("empty-state").style.display = "none";
document.getElementById("score-bar").style.display = "flex";
document.getElementById("filters").style.display = "flex";
// Meta
document.getElementById("meta").textContent = `${results.total_clauses} clauses · ${results.flagged_count} flagged`;
// Grade
const gb = document.getElementById("grade-box");
gb.textContent = results.grade;
gb.className = `grade-box grade-${results.grade.toLowerCase()}`;
// Score
document.getElementById("score-num").textContent = `${results.risk_score} / 100`;
const pf = document.getElementById("progress-fill");
pf.style.width = `${results.risk_score}%`;
pf.style.background = results.risk_score >= 60 ? "#ef4444" : results.risk_score >= 30 ? "#f59e0b" : "#22c55e";
// FIX v4.3: Count CRITICAL severity too
const counts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
const flagged = results.results.filter(r => r.categories?.length > 0);
flagged.forEach(r => r.categories.forEach(c => {
if (counts[c.severity] !== undefined) counts[c.severity]++;
else counts.MEDIUM++; // Default unknown to MEDIUM
}));
// Show CRITICAL count in the filter if any exist
const fcCrit = document.getElementById("fc-crit");
const critFilter = document.getElementById("filter-critical");
if (fcCrit) fcCrit.textContent = counts.CRITICAL;
if (critFilter) critFilter.style.display = counts.CRITICAL > 0 ? "flex" : "none";
document.getElementById("fc-high").textContent = counts.HIGH;
document.getElementById("fc-med").textContent = counts.MEDIUM;
document.getElementById("fc-low").textContent = counts.LOW;
allClauses = flagged;
renderClauses();
}
function renderClauses() {
const list = document.getElementById("clause-list");
const filtered = currentFilter === "all" ? allClauses : allClauses.filter(c => c.categories.some(cat => cat.severity === currentFilter));
if (filtered.length === 0) {
list.innerHTML = '<div style="text-align:center;padding:24px;color:#a1a1aa;font-size:12px;">No clauses match this filter.</div>';
return;
}
list.innerHTML = filtered.map((clause, i) => {
const maxSev = clause.categories.reduce((m, c) => {
return (SEV_ORDER[c.severity] || 0) > (SEV_ORDER[m] || 0) ? c.severity : m;
}, "LOW");
const tagMap = { CRITICAL: "tag-critical", HIGH: "tag-high", MEDIUM: "tag-medium", LOW: "tag-low" };
const tags = clause.categories.map(c =>
`<span class="tag ${tagMap[c.severity] || "tag-medium"}">${SEV_ICONS[c.severity] || ""} ${c.name}</span>`
).join("");
const descs = clause.categories.map(c =>
`<div class="clause-desc">${DESCS[c.name] || c.name}</div>`
).join("");
const text = clause.text.length > 200 ? clause.text.slice(0, 200) + "…" : clause.text;
return `
<div class="clause-card sev-${maxSev.toLowerCase()}">
<div class="clause-tags">${tags}</div>
<div class="clause-text">${text}</div>
${descs}
</div>
`;
}).join("");
}
// Filters
document.getElementById("filters").addEventListener("click", (e) => {
const btn = e.target.closest(".filter-btn");
if (!btn) return;
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
currentFilter = btn.dataset.filter;
renderClauses();
});
// Listen for updates
chrome.storage.onChanged.addListener((changes) => {
for (const key of Object.keys(changes)) {
if (key.startsWith("results_")) { loadResults(); break; }
}
});
loadResults();