feat(browser-matrix): Cross-Browser-Befunde + Browser-Default-Einordnung (Phase 4)

- browser_cross_finding: deterministische Sicht ueber die Matrix (keine 2.
  Engine, kein LLM). Findet Inkonsistenzen ZWISCHEN Browsern (Cookies vor
  Consent / Ablehnen nicht universell respektiert / Banner-Links fehlend) und
  ordnet ein: Safari-ITP / Brave-Shields / Firefox-ETP maskieren Verstoesse
  clientseitig → strenge Engine "sauber" ist KEIN Compliance-Beleg, massgeblich
  sind die nachgiebigen (Chrome/Edge). Coverage-Hinweis fuer nicht verfuegbare
  Browser. Je Befund Titel/Detail/Severity/affected/Massnahme.
- snapshot_check_routes: cross_findings frisch in run + GET (nicht persistiert).
- BrowserBehaviorView: "Cross-Browser-Befunde"-Block ueber der Tabelle.
- Tests: test_browser_cross_finding (6).

Offen (Folge-Task): Borlabs-Consent-Historie-Live-Erkennung (braucht
consent-tester-Storage-Scan).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-12 23:22:57 +02:00
parent 9587726936
commit 85a8a1d545
4 changed files with 277 additions and 2 deletions
@@ -22,7 +22,11 @@ type Row = {
profile_id: string; label: string; engine?: string; is_mobile?: boolean
score?: number; verbal?: string; summary?: Summary | null; error?: string
}
type Matrix = { browser_matrix?: Row[]; aggregate?: Record<string, unknown>; url?: string; scanned_at?: string }
type CrossFinding = { title: string; detail?: string; severity: string; affected?: string[]; measure?: string }
type Matrix = {
browser_matrix?: Row[]; aggregate?: Record<string, unknown>
url?: string; scanned_at?: string; cross_findings?: CrossFinding[]
}
const sevCls = (s: string) => {
const u = (s || '').toUpperCase()
@@ -119,6 +123,30 @@ export function BrowserBehaviorView({ snapshotId }: { snapshotId: string }) {
</div>
{error && <div className="text-sm text-red-600">{error}</div>}
{/* Cross-Browser-Befunde — der Mehrwert ggü. Einzel-Browser-Scan */}
{(matrix.cross_findings?.length ?? 0) > 0 && (
<div className="space-y-2">
<h3 className="text-sm font-semibold text-gray-900">Cross-Browser-Befunde</h3>
{matrix.cross_findings!.map((f, i) => (
<div key={i} className="border border-gray-200 rounded-xl p-3 space-y-1">
<div className="flex items-center gap-2 flex-wrap">
<span className={`text-[10px] px-1.5 py-0.5 rounded uppercase ${sevCls(f.severity)}`}>{f.severity}</span>
<span className="text-sm font-medium text-gray-900">{f.title}</span>
</div>
{f.detail && <p className="text-sm text-gray-600">{f.detail}</p>}
{(f.affected?.length ?? 0) > 0 && (
<div className="flex gap-1 flex-wrap">
{f.affected!.map((a, j) => (
<span key={j} className="text-[10px] px-1.5 py-0.5 rounded bg-gray-100 text-gray-600">{a}</span>
))}
</div>
)}
{f.measure && <p className="text-sm text-gray-700"><span className="text-gray-400">Maßnahme: </span>{f.measure}</p>}
</div>
))}
</div>
)}
<div className="overflow-x-auto border border-gray-200 rounded-xl">
<table className="w-full text-sm">
<thead className="bg-gray-50 text-gray-500 text-xs">