Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,31 @@
font-family: var(--font-mono);
}

.complexity-chip {
padding: 2px 10px;
border-radius: 99px;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.02em;
cursor: help;
white-space: nowrap;
}

.complexity-chip.complexity-easy {
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
}

.complexity-chip.complexity-medium {
background: rgba(245, 200, 66, 0.15);
color: var(--yellow);
}

.complexity-chip.complexity-hard {
background: rgba(242, 87, 87, 0.15);
color: var(--red);
}

.issue-type {
font-size: 0.88rem;
font-weight: 600
Expand Down Expand Up @@ -4066,6 +4091,51 @@ <h3>Project Health Score</h3>
`;
}

// ── Issue Complexity Badge ──
// Pure frontend heuristic: scores an issue using fields already present on the
// issue object (severity, description length, suggestion presence, keywords)
// and maps it to Easy / Medium / Hard. No backend involvement required.
const COMPLEXITY_KEYWORDS = {
hard: ['race condition', 'deadlock', 'memory leak', 'security', 'vulnerability', 'concurrency', 'thread', 'async', 'architecture', 'refactor', 'sql injection', 'recursion', 'algorithm'],
medium: ['null', 'undefined', 'exception', 'error handling', 'type', 'performance', 'validation', 'edge case', 'logic'],
};

function scoreIssueComplexity(issue) {
let score = 0;
const desc = (issue.description || '').toLowerCase();
const type = (issue.type || '').toLowerCase();
const text = `${desc} ${type}`;

if (issue.severity === 'error') score += 3;
else if (issue.severity === 'warning') score += 2;
else if (issue.severity === 'info') score += 1;

const len = desc.length;
if (len > 160) score += 3;
else if (len > 80) score += 2;
else if (len > 0) score += 1;

if (COMPLEXITY_KEYWORDS.hard.some(k => text.includes(k))) score += 3;
else if (COMPLEXITY_KEYWORDS.medium.some(k => text.includes(k))) score += 1;

if (!issue.suggestion) score += 1;

return score;
}

function getIssueComplexity(issue) {
const score = scoreIssueComplexity(issue);
if (score >= 7) return { level: 'hard', label: 'Hard', emoji: 'πŸ”΄', score };
if (score >= 4) return { level: 'medium', label: 'Medium', emoji: '🟑', score };
return { level: 'easy', label: 'Easy', emoji: '🟒', score };
}

function renderComplexityBadge(issue) {
const c = getIssueComplexity(issue);
const tooltip = `Estimated complexity: ${c.label} (score ${c.score}/11) β€” based on severity, description length, keywords, and whether a fix suggestion is provided.`;
return `<span class="complexity-chip complexity-${c.level}" title="${escHtml(tooltip)}">${c.emoji} ${c.label}</span>`;
}

function renderDebug(dbg) {
document.getElementById('emptyDebug').style.display = 'none';
const el = document.getElementById('debugResult');
Expand Down Expand Up @@ -4106,6 +4176,7 @@ <h3>Project Health Score</h3>

<summary class="collapsible-summary">
<span>${issue.type}</span>
${renderComplexityBadge(issue)}
${issue.line
? `<span class="issue-line">Line ${issue.line}</span>`
: ''}
Expand Down Expand Up @@ -5876,4 +5947,4 @@ <h2 id="collabDrawerTitle">Live Collaboration</h2>

</body>

</html>
</html>
54 changes: 53 additions & 1 deletion frontend/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,55 @@ async function runAnalysis() {
}
}

// ── Issue Complexity Badge ──
// Pure frontend heuristic: scores an issue using fields already present on the
// issue object (severity, description length, suggestion presence, keywords)
// and maps it to Easy / Medium / Hard. No backend involvement required.
const COMPLEXITY_KEYWORDS = {
hard: ['race condition', 'deadlock', 'memory leak', 'security', 'vulnerability', 'concurrency', 'thread', 'async', 'architecture', 'refactor', 'sql injection', 'recursion', 'algorithm'],
medium: ['null', 'undefined', 'exception', 'error handling', 'type', 'performance', 'validation', 'edge case', 'logic'],
};

function scoreIssueComplexity(issue) {
let score = 0;
const desc = (issue.description || '').toLowerCase();
const type = (issue.type || '').toLowerCase();
const text = `${desc} ${type}`;

// Severity contributes the most signal when available.
if (issue.severity === 'error') score += 3;
else if (issue.severity === 'warning') score += 2;
else if (issue.severity === 'info') score += 1;

// Longer descriptions tend to indicate more nuanced/complex issues.
const len = desc.length;
if (len > 160) score += 3;
else if (len > 80) score += 2;
else if (len > 0) score += 1;

// Keyword heuristics.
if (COMPLEXITY_KEYWORDS.hard.some(k => text.includes(k))) score += 3;
else if (COMPLEXITY_KEYWORDS.medium.some(k => text.includes(k))) score += 1;

// A missing fix suggestion usually means the issue needs more judgment to resolve.
if (!issue.suggestion) score += 1;

return score;
}

function getIssueComplexity(issue) {
const score = scoreIssueComplexity(issue);
if (score >= 7) return { level: 'hard', label: 'Hard', emoji: 'πŸ”΄', score };
if (score >= 4) return { level: 'medium', label: 'Medium', emoji: '🟑', score };
return { level: 'easy', label: 'Easy', emoji: '🟒', score };
}

function renderComplexityBadge(issue) {
const c = getIssueComplexity(issue);
const tooltip = `Estimated complexity: ${c.label} (score ${c.score}/11) β€” based on severity, description length, keywords, and whether a fix suggestion is provided.`;
return `<span class="result-tag complexity-badge complexity-${c.level}" title="${escHtml(tooltip)}">${c.emoji} ${c.label}</span>`;
}

// ── Render Output ──
function renderResult(data, mode) {
let html = '';
Expand Down Expand Up @@ -457,6 +506,7 @@ function renderResult(data, mode) {
? '<span class="result-tag tag-ok">βœ“ No issues found</span>'
: issues.map(i => `<div style="margin-bottom:10px">
<span class="result-tag tag-error">${escHtml(i.type || 'Issue')}</span>
${renderComplexityBadge(i)}
<p style="margin-top:4px">${escHtml(i.description || '')}</p>
${i.suggestion ? `<p style="color:var(--accent-green);margin-top:4px">Fix: ${escHtml(i.suggestion)}</p>` : ''}
</div>`).join('')}
Expand Down Expand Up @@ -503,6 +553,7 @@ function renderResult(data, mode) {
: issues.map(i => `<div style="margin-bottom:14px;padding:12px;background:var(--bg-2);border-radius:6px;border:1px solid var(--border)">
<span class="result-tag tag-error">${escHtml(i.type || 'Issue')}</span>
${i.line ? `<span class="result-tag tag-info">Line ${i.line}</span>` : ''}
${renderComplexityBadge(i)}
<p style="margin-top:8px">${escHtml(i.description || '')}</p>
${i.suggestion ? `<p style="margin-top:6px;color:var(--accent-green)">β†’ ${escHtml(i.suggestion)}</p>` : ''}
</div>`).join('')}
Expand Down Expand Up @@ -636,4 +687,5 @@ if (digestForm) {

// ── Init ──
renderHistory();
renderFavorites();
renderFavorites();

10 changes: 10 additions & 0 deletions frontend/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,15 @@ body {
.tag-ok { background: rgba(34,197,94,0.12); color: #86efac; border: 1px solid rgba(34,197,94,0.2); }
.tag-info { background: rgba(59,130,246,0.12); color: #93c5fd; border: 1px solid rgba(59,130,246,0.2); }

/* ── Issue Complexity Badge ── */
.complexity-badge {
font-weight: 600;
cursor: help;
}
.complexity-easy { background: rgba(34,197,94,0.12); color: #86efac; border: 1px solid rgba(34,197,94,0.25); }
.complexity-medium { background: rgba(234,179,8,0.14); color: #fde047; border: 1px solid rgba(234,179,8,0.3); }
.complexity-hard { background: rgba(239,68,68,0.14); color: #f87171; border: 1px solid rgba(239,68,68,0.3); }

/* ── History ── */
.history-section, .fav-section { padding: 4rem 4rem; }
.history-container, .fav-container {
Expand Down Expand Up @@ -611,3 +620,4 @@ body {
text-align: center;
}
}