diff --git a/frontend/src/pages/ConfigPage.tsx b/frontend/src/pages/ConfigPage.tsx
index 1cbd2fb0..3a0e3d95 100644
--- a/frontend/src/pages/ConfigPage.tsx
+++ b/frontend/src/pages/ConfigPage.tsx
@@ -170,7 +170,7 @@ function KvGrid({ entries }: { entries: Array<[string, React.ReactNode]> }) {
}
function fmt(v: unknown): React.ReactNode {
- if (v === null || v === undefined) return
@@ -387,7 +387,7 @@ function RawEditor() {
value={draft ?? ''}
spellCheck={false}
onChange={(e) => setDraft(e.target.value)}
- placeholder="# nyx.local — overrides for the default config.
# Anything you set here wins over nyx.conf."
+ placeholder="# nyx.local - overrides for the default config.
# Anything you set here wins over nyx.conf."
/>
Edits are validated against the full config schema before being written.
@@ -608,7 +608,7 @@ export function ConfigPage() {
setProfileName('');
}, [profileName, addProfile]);
- if (configLoading) return ;
+ if (configLoading) return ;
if (configError) return ;
const cfg = config as Record> | undefined;
@@ -616,15 +616,7 @@ export function ConfigPage() {
const triageSyncOn = !!server?.triage_sync;
return (
- <>
-
-
Config
-
- Edit defaults, rules, profiles, and the raw nyx.local{' '}
- file
-
-
-
+
{(
[
@@ -866,6 +858,6 @@ export function ConfigPage() {
)}
{tab === 'raw' && }
- >
+
);
}
diff --git a/frontend/src/pages/FindingDetailPage.tsx b/frontend/src/pages/FindingDetailPage.tsx
index 57c49bd7..e285dfd3 100644
--- a/frontend/src/pages/FindingDetailPage.tsx
+++ b/frontend/src/pages/FindingDetailPage.tsx
@@ -67,7 +67,7 @@ const STATE_REMEDIATION_HINTS: Record
= {
'Prefer a language-native cleanup pattern (defer, with, try-with-resources, RAII).',
],
'state-resource-leak-possible': [
- 'Ensure the resource is closed on all code paths — including error and early-return paths.',
+ 'Ensure the resource is closed on all code paths, including error and early-return paths.',
'Put cleanup in a finally/defer block rather than after the happy path.',
],
'state-unauthed-access': [
@@ -636,7 +636,7 @@ const TAINT_REMEDIATION: Record = {
'If HTML is unavoidable, run input through a well-maintained sanitizer (DOMPurify, Bleach).',
],
sql: [
- 'Use parameterized queries or a prepared statement — never concatenate user input into SQL.',
+ 'Use parameterized queries or a prepared statement. Never concatenate user input into SQL.',
'Prefer an ORM or query builder that escapes parameters automatically.',
'Validate input type (integer, enum, allowlist) before the query.',
],
@@ -878,6 +878,12 @@ export function FindingDetailPage() {
const hasRelated = f.related_findings && f.related_findings.length > 0;
const hasLabels = f.labels && f.labels.length > 0;
const hasCode = !!f.code_context;
+ const sourcePath = evidence?.source
+ ? `${evidence.source.path}:${evidence.source.line}:${evidence.source.col}`
+ : null;
+ const sinkPath = evidence?.sink
+ ? `${evidence.sink.path}:${evidence.sink.line}:${evidence.sink.col}`
+ : null;
const metaParts: string[] = [];
if (f.category) metaParts.push(f.category);
@@ -894,7 +900,7 @@ export function FindingDetailPage() {
}
return (
-
+
+ {(sourcePath || sinkPath) && (
+
+
+ Source
+ {sourcePath || 'Unknown'}
+
+
+ →
+
+
+ Sink
+ {sinkPath || 'Unknown'}
+
+
+ )}
+
- {/* Evidence (collapsed by default — overlaps with taint flow) */}
- {hasEvidence && (
-
-
-
- )}
-
{/* Analysis Notes */}
{hasNotes && (
@@ -1002,20 +1017,6 @@ export function FindingDetailPage() {
)}
- {/* Labels */}
- {hasLabels && (
-
-
- {f.labels.map(([k, v], i) => (
-
- {k}:{' '}
- {v}
-
- ))}
-
-
- )}
-
{/* Code Preview */}
{hasCode && (
diff --git a/frontend/src/pages/FindingsPage.tsx b/frontend/src/pages/FindingsPage.tsx
index b6ee69d4..5f9eee96 100644
--- a/frontend/src/pages/FindingsPage.tsx
+++ b/frontend/src/pages/FindingsPage.tsx
@@ -567,15 +567,7 @@ export function FindingsPage() {
const totalPages = Math.ceil(data.total / data.per_page) || 1;
return (
- <>
-
-
Findings
-
- {data.total} finding{data.total !== 1 ? 's' : ''}
- {hasActiveFilters ? ' (filtered)' : ''}
-
-
-
+
{/* Filter bar */}
setSuppressModalOpen(false)}
/>
)}
- >
+
);
}
diff --git a/frontend/src/pages/OverviewPage.tsx b/frontend/src/pages/OverviewPage.tsx
index e10e6654..f030ba34 100644
--- a/frontend/src/pages/OverviewPage.tsx
+++ b/frontend/src/pages/OverviewPage.tsx
@@ -64,7 +64,7 @@ export function OverviewPage() {
const categoryItems = (overview.issue_categories || [])
.slice(0, 8)
- .map((b) => ({ label: b.label, value: b.count, color: '#72f3d7' }));
+ .map((b) => ({ label: b.label, value: b.count, color: 'var(--accent)' }));
const trendData = (trends || []).map((t) => ({
label: t.timestamp,
@@ -74,11 +74,7 @@ export function OverviewPage() {
const hotSinks = overview.hot_sinks || [];
return (
- <>
-
-
Overview
-
-
+
{/* Baseline strip */}
)}
- {/* Stat cards — kept lean: 5 cards, severity stacks live in Top Files
+ {/* Stat cards kept lean: 5 cards, severity stacks live in Top Files
and Per-Language. Cross-file / Symex moved into Scanner Quality. */}
- {/* Charts */}
+ {/* Charts — 3-col: Findings (col1 span2) | OWASP+Confidence (col2) | Categories (col3 span2) */}
Findings Over Time
@@ -158,14 +154,8 @@ export function OverviewPage() {
)}
-
OWASP Top 10 (2021)
- {overview.owasp_buckets && overview.owasp_buckets.length > 0 ? (
-
- ) : (
-
-
No OWASP-mapped findings.
-
- )}
+
Issue Categories
+
Confidence Distribution
@@ -180,8 +170,14 @@ export function OverviewPage() {
)}
-
Issue Categories
-
+
OWASP Top 10 (2021)
+ {overview.owasp_buckets && overview.owasp_buckets.length > 0 ? (
+
+ ) : (
+
+
No OWASP-mapped findings.
+
+ )}
@@ -289,7 +285,7 @@ export function OverviewPage() {
)}
- >
+
);
}
diff --git a/frontend/src/pages/RulesPage.tsx b/frontend/src/pages/RulesPage.tsx
index 33cff45f..3cbcea4b 100644
--- a/frontend/src/pages/RulesPage.tsx
+++ b/frontend/src/pages/RulesPage.tsx
@@ -271,20 +271,7 @@ export function RulesPage() {
if (error) return
;
return (
- <>
-
-
Rules
-
- {(rules || []).length} rules
-
-
-
+
);
}
diff --git a/frontend/src/pages/ScanComparePage.tsx b/frontend/src/pages/ScanComparePage.tsx
index 8ae61b41..f1713c38 100644
--- a/frontend/src/pages/ScanComparePage.tsx
+++ b/frontend/src/pages/ScanComparePage.tsx
@@ -221,7 +221,7 @@ function CompareByGroup({
}, [data, groupField]);
return (
- <>
+
{groups.map(([key, items]) => {
const counts = { new: 0, fixed: 0, changed: 0, unchanged: 0 };
items.forEach(
@@ -267,7 +267,7 @@ function CompareByGroup({
);
})}
- >
+
);
}
@@ -300,16 +300,10 @@ export function ScanComparePage() {
return (
<>
-
-
-
-
Scan Comparison
+
+
diff --git a/frontend/src/pages/ScanDetailPage.tsx b/frontend/src/pages/ScanDetailPage.tsx
index 1ed20807..9f590f70 100644
--- a/frontend/src/pages/ScanDetailPage.tsx
+++ b/frontend/src/pages/ScanDetailPage.tsx
@@ -71,125 +71,127 @@ function SummaryTab({ scan }: { scan: ScanView }) {
-
-
Details
-
-
-
- |
- Scan ID
- |
-
- {scan.id}
- |
-
-
- | Root |
-
- {scan.scan_root}
- |
-
-
- | Engine |
- {scan.engine_version || '-'} |
-
-
- | Started |
- {fmtDate(scan.started_at)} |
-
-
- | Finished |
- {fmtDate(scan.finished_at)} |
-
- {scan.error && (
+
+
+
Details
+
+
- | Error |
- {scan.error} |
+
+ Scan ID
+ |
+
+ {scan.id}
+ |
- )}
-
-
-
-
- {timing && total > 0 && (
-
-
Timing Breakdown
-
-
-
- {' '}
- Walk {timing.walk_ms}ms
-
-
- {' '}
- Pass 1 {timing.pass1_ms}ms
-
-
- {' '}
- Call Graph {timing.call_graph_ms}ms
-
-
- {' '}
- Pass 2 {timing.pass2_ms}ms
-
-
- {' '}
- Post {timing.post_process_ms}ms
-
-
+
+ | Root |
+
+ {scan.scan_root}
+ |
+
+
+ | Engine |
+ {scan.engine_version || '-'} |
+
+
+ | Started |
+ {fmtDate(scan.started_at)} |
+
+
+ | Finished |
+ {fmtDate(scan.finished_at)} |
+
+ {scan.error && (
+
+ | Error |
+ {scan.error} |
+
+ )}
+
+
- )}
+
+ {timing && total > 0 && (
+
+
Timing Breakdown
+
+
+
+ {' '}
+ Walk {timing.walk_ms}ms
+
+
+ {' '}
+ Pass 1 {timing.pass1_ms}ms
+
+
+ {' '}
+ Call Graph {timing.call_graph_ms}ms
+
+
+ {' '}
+ Pass 2 {timing.pass2_ms}ms
+
+
+ {' '}
+ Post {timing.post_process_ms}ms
+
+
+
+ )}
+
>
);
}
@@ -212,7 +214,7 @@ function FindingsTab({ scanId }: { scanId: string }) {
}
return (
- <>
+
@@ -266,7 +268,7 @@ function FindingsTab({ scanId }: { scanId: string }) {
>
Showing {data.findings.length} of {data.total} findings
- >
+
);
}
@@ -416,31 +418,19 @@ export function ScanDetailPage() {
return (
<>
-
+
{prevScanId && (
)}
-
-
-
-
Scan Detail
-
+
{scan.status}
diff --git a/frontend/src/pages/ScansPage.tsx b/frontend/src/pages/ScansPage.tsx
index dd0f25b0..636b67bf 100644
--- a/frontend/src/pages/ScansPage.tsx
+++ b/frontend/src/pages/ScansPage.tsx
@@ -174,11 +174,7 @@ export function ScansPage() {
const showCheckboxes = completedScans.length >= 2;
return (
- <>
-
-
Scans
-
-
+
{(runningScans.length > 0 || isScanRunning) && scanProgress && (
)}
@@ -210,90 +206,103 @@ export function ScansPage() {
) : (
-
+
)}
- >
+
);
}
diff --git a/frontend/src/pages/TriagePage.tsx b/frontend/src/pages/TriagePage.tsx
index a706a1f0..baab55b1 100644
--- a/frontend/src/pages/TriagePage.tsx
+++ b/frontend/src/pages/TriagePage.tsx
@@ -130,6 +130,22 @@ function TriageSummary({
{headline}
+ {showSeverity && totalCount > 0 && (
+
+ {SEVERITIES.map((sev) => (
+
+
+
+ {(openBySev[sev] ?? 0).toLocaleString()}
+
+ {sev}
+
+ ))}
+
+ )}
{totalCount > 0 && (
- {showSeverity && totalCount > 0 && (
-
- {SEVERITIES.map((sev) => (
-
-
-
- {(openBySev[sev] ?? 0).toLocaleString()}
-
- {sev}
-
- ))}
-
- )}
{expanded && (