fix(checklist-ui): show INFO-severity checks as gray info icon
Build + Deploy / build-admin-compliance (push) Successful in 2m7s
Build + Deploy / build-backend-compliance (push) Successful in 3m20s
Build + Deploy / build-ai-sdk (push) Successful in 1m2s
Build + Deploy / build-developer-portal (push) Successful in 1m14s
Build + Deploy / build-tts (push) Successful in 1m45s
Build + Deploy / build-document-crawler (push) Successful in 48s
Build + Deploy / build-dsms-gateway (push) Successful in 37s
Build + Deploy / build-dsms-node (push) Successful in 23s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m44s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 49s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 27s
CI / test-python-dsms-gateway (push) Successful in 23s
CI / validate-canonical-controls (push) Successful in 14s
Build + Deploy / trigger-orca (push) Failing after 32s

INFO checks (V.i.S.d.P., Streitbeilegung, Berufsrecht, Stammkapital,
etc.) that fail are now shown with a gray info icon instead of red X,
with gray hint text. They are excluded from the Pflichtangaben count
since they are context-dependent and likely not applicable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-12 12:28:00 +02:00
parent baca0f6b80
commit 128967fa3d
@@ -46,7 +46,7 @@ function groupChecks(checks: CheckItem[]): GroupedCheck[] {
})) }))
} }
function CheckIcon({ passed, skipped }: { passed: boolean; skipped?: boolean }) { function CheckIcon({ passed, skipped, isInfo }: { passed: boolean; skipped?: boolean; isInfo?: boolean }) {
if (skipped) { if (skipped) {
return ( return (
<svg className="w-4 h-4 text-gray-300 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-gray-300 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -61,6 +61,13 @@ function CheckIcon({ passed, skipped }: { passed: boolean; skipped?: boolean })
</svg> </svg>
) )
} }
if (isInfo) {
return (
<svg className="w-4 h-4 text-gray-400 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)
}
return ( return (
<svg className="w-4 h-4 text-red-500 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4 text-red-500 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -104,8 +111,9 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
const typeLabel = DOC_TYPE_LABELS[r.doc_type] || r.doc_type const typeLabel = DOC_TYPE_LABELS[r.doc_type] || r.doc_type
const grouped = groupChecks(r.checks) const grouped = groupChecks(r.checks)
const l1Checks = r.checks.filter(c => (c.level ?? 1) === 1) const l1Checks = r.checks.filter(c => (c.level ?? 1) === 1)
const l1Scoreable = l1Checks.filter(c => c.severity !== 'INFO')
const l2Active = r.checks.filter(c => (c.level ?? 1) === 2 && !c.skipped) const l2Active = r.checks.filter(c => (c.level ?? 1) === 2 && !c.skipped)
const l1Passed = l1Checks.filter(c => c.passed).length const l1Passed = l1Scoreable.filter(c => c.passed).length
const l2Passed = l2Active.filter(c => c.passed).length const l2Passed = l2Active.filter(c => c.passed).length
return ( return (
@@ -126,7 +134,7 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
<div className="text-sm font-medium text-gray-900 truncate">{r.label}</div> <div className="text-sm font-medium text-gray-900 truncate">{r.label}</div>
<div className="text-xs text-gray-500 truncate"> <div className="text-xs text-gray-500 truncate">
{l1Checks.length > 0 {l1Checks.length > 0
? `${l1Passed}/${l1Checks.length} Pflichtangaben` ? `${l1Passed}/${l1Scoreable.length} Pflichtangaben`
+ (l2Active.length > 0 ? `, ${l2Passed}/${l2Active.length} Detailpruefungen` : '') + (l2Active.length > 0 ? `, ${l2Passed}/${l2Active.length} Detailpruefungen` : '')
: r.url} : r.url}
</div> </div>
@@ -164,13 +172,18 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
<p className="text-sm text-red-600">{r.error}</p> <p className="text-sm text-red-600">{r.error}</p>
) : ( ) : (
<div className="space-y-1"> <div className="space-y-1">
{grouped.map((g) => ( {grouped.map((g) => {
const l1Info = g.check.severity === 'INFO' && !g.check.passed
return (
<div key={g.check.id}> <div key={g.check.id}>
{/* L1 check */} {/* L1 check */}
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<CheckIcon passed={g.check.passed} /> <CheckIcon passed={g.check.passed} isInfo={l1Info} />
<div className="flex-1"> <div className="flex-1">
<div className={`text-sm ${g.check.passed ? 'text-gray-700' : 'text-red-700 font-medium'}`}> <div className={`text-sm ${
g.check.passed ? 'text-gray-700'
: l1Info ? 'text-gray-500' : 'text-red-700 font-medium'
}`}>
{g.check.label} {g.check.label}
{g.children.length > 0 && <L2Summary>{g.children}</L2Summary>} {g.children.length > 0 && <L2Summary>{g.children}</L2Summary>}
</div> </div>
@@ -180,7 +193,7 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
</div> </div>
)} )}
{!g.check.passed && g.check.hint && ( {!g.check.passed && g.check.hint && (
<div className="text-xs text-red-600/80 mt-0.5"> <div className={`text-xs mt-0.5 ${l1Info ? 'text-gray-400' : 'text-red-600/80'}`}>
{g.check.hint} {g.check.hint}
</div> </div>
)} )}
@@ -190,13 +203,16 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
{/* L2 children — always visible */} {/* L2 children — always visible */}
{g.children.length > 0 && ( {g.children.length > 0 && (
<div className="ml-6 mt-0.5 mb-1 space-y-0.5 border-l-2 border-gray-200 pl-3"> <div className="ml-6 mt-0.5 mb-1 space-y-0.5 border-l-2 border-gray-200 pl-3">
{g.children.map((ch) => ( {g.children.map((ch) => {
const chInfo = ch.severity === 'INFO' && !ch.passed && !ch.skipped
return (
<div key={ch.id} className="flex items-start gap-2"> <div key={ch.id} className="flex items-start gap-2">
<CheckIcon passed={ch.passed} skipped={ch.skipped} /> <CheckIcon passed={ch.passed} skipped={ch.skipped} isInfo={chInfo} />
<div className="flex-1"> <div className="flex-1">
<div className={`text-xs ${ <div className={`text-xs ${
ch.skipped ? 'text-gray-400 italic' ch.skipped ? 'text-gray-400 italic'
: ch.passed ? 'text-gray-600' : 'text-red-600 font-medium' : ch.passed ? 'text-gray-600'
: chInfo ? 'text-gray-400' : 'text-red-600 font-medium'
}`}> }`}>
{ch.label} {ch.label}
{ch.skipped && ' (uebersprungen)'} {ch.skipped && ' (uebersprungen)'}
@@ -207,17 +223,19 @@ export function ChecklistView({ results }: { results: DocResult[] }) {
</div> </div>
)} )}
{!ch.passed && !ch.skipped && ch.hint && ( {!ch.passed && !ch.skipped && ch.hint && (
<div className="text-xs text-red-500/80 mt-0.5"> <div className={`text-xs mt-0.5 ${chInfo ? 'text-gray-400' : 'text-red-500/80'}`}>
{ch.hint} {ch.hint}
</div> </div>
)} )}
</div> </div>
</div> </div>
))} )
})}
</div> </div>
)} )}
</div> </div>
))} )
})}
{r.word_count > 0 && ( {r.word_count > 0 && (
<div className="text-xs text-gray-400 mt-2 pt-2 border-t border-gray-200"> <div className="text-xs text-gray-400 mt-2 pt-2 border-t border-gray-200">
{r.word_count} Woerter analysiert {r.word_count} Woerter analysiert