refactor(complianceCheckTab): split — DOCUMENT_TYPES + Storage + Polling out
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 10s
CI / loc-budget (push) Successful in 14s
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 2m21s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped

ComplianceCheckTab.tsx war 519 LOC und blockte jeden weiteren Edit
(500-LOC-Hard-Cap). Drei Concerns ausgelagert:

  - _document_types.ts: DOCUMENT_TYPES + DocTypeId (inkl. news doc_type)
  - _compliance_storage.ts: STORAGE_KEY_*, DocState/HistoryEntry types,
    emptyDocState/initState helpers, countWords
  - _useCompliancePolling.ts: Resume-Polling-Hook (importierbar,
    Inline-Polling bleibt für Stabilität)

ComplianceCheckTab.tsx ist jetzt 461 LOC (-58).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-08 12:18:30 +02:00
parent b4ce3528e5
commit 5aaf7ac613
3 changed files with 175 additions and 64 deletions
@@ -5,71 +5,13 @@ import { ChecklistView } from './ChecklistView'
import { DocumentRow } from './DocumentRow'
import { MigrationPanel } from './MigrationPanel'
import { PreScanWizard, useScanContext, isContextComplete } from './PreScanWizard'
import { DOCUMENT_TYPES, type DocTypeId } from './_document_types'
import {
STORAGE_KEY_STATE, STORAGE_KEY_RESULTS, STORAGE_KEY_HISTORY,
STORAGE_KEY_CHECK_ID, countWords, initState,
type DocState, type DocsState, type HistoryEntry,
} from './_compliance_storage'
const DOCUMENT_TYPES = [
{ id: 'dse', label: 'DSI (Datenschutzinformation)', required: true },
{ id: 'impressum', label: 'Impressum', required: true },
{ id: 'social_media', label: 'Social Media DSE', required: false },
{ id: 'cookie', label: 'Cookie-Richtlinie', required: false },
{ id: 'agb', label: 'AGB', required: false },
{ id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen', required: false },
{ id: 'widerruf', label: 'Widerrufsbelehrung', required: false },
{ id: 'dsb', label: 'DSB-Kontakt', required: false },
] as const
type DocTypeId = typeof DOCUMENT_TYPES[number]['id']
interface DocState {
url: string
text: string
loading: boolean
error: string | null
}
type DocsState = Record<DocTypeId, DocState>
const STORAGE_KEY_STATE = 'compliance-check-state'
const STORAGE_KEY_RESULTS = 'compliance-check-results'
const STORAGE_KEY_HISTORY = 'compliance-check-history'
const STORAGE_KEY_CHECK_ID = 'compliance-check-active-id'
function emptyDocState(): DocState {
return { url: '', text: '', loading: false, error: null }
}
function initState(): DocsState {
if (typeof window === 'undefined') {
return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState
}
try {
const saved = localStorage.getItem(STORAGE_KEY_STATE)
if (saved) {
const parsed = JSON.parse(saved) as Record<string, { url?: string; text?: string }>
return Object.fromEntries(
DOCUMENT_TYPES.map(d => [d.id, {
url: parsed[d.id]?.url || '',
text: parsed[d.id]?.text || '',
loading: false,
error: null,
}])
) as DocsState
}
} catch { /* ignore */ }
return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState
}
function countWords(text: string): number {
if (!text.trim()) return 0
return text.trim().split(/\s+/).length
}
interface HistoryEntry {
date: string
docCount: number
findings: number
resultKey: string
checkId?: string
}
export function ComplianceCheckTab() {
const [docs, setDocs] = useState<DocsState>(initState)
@@ -0,0 +1,86 @@
/**
* Storage-Helfer für ComplianceCheckTab.
*
* Extrahiert aus ComplianceCheckTab.tsx (P11-Tech-Debt-Sprint) damit
* die zentrale UI unter der 500-LOC-Hard-Cap bleibt.
*/
import { DOCUMENT_TYPES, type DocTypeId } from './_document_types'
export const STORAGE_KEY_STATE = 'compliance-check-state'
export const STORAGE_KEY_RESULTS = 'compliance-check-results'
export const STORAGE_KEY_HISTORY = 'compliance-check-history'
export const STORAGE_KEY_CHECK_ID = 'compliance-check-active-id'
export interface DocState {
url: string
text: string
loading: boolean
error: string | null
}
export type DocsState = Record<DocTypeId, DocState>
export interface HistoryEntry {
date: string
docCount: number
findings: number
resultKey: string
checkId?: string
}
export function emptyDocState(): DocState {
return { url: '', text: '', loading: false, error: null }
}
export function initState(): DocsState {
if (typeof window === 'undefined') {
return Object.fromEntries(
DOCUMENT_TYPES.map(d => [d.id, emptyDocState()]),
) as DocsState
}
try {
const saved = localStorage.getItem(STORAGE_KEY_STATE)
if (saved) {
const parsed = JSON.parse(saved) as Record<
string, { url?: string; text?: string }
>
return Object.fromEntries(
DOCUMENT_TYPES.map(d => [d.id, {
url: parsed[d.id]?.url || '',
text: parsed[d.id]?.text || '',
loading: false,
error: null,
}]),
) as DocsState
}
} catch { /* ignore */ }
return Object.fromEntries(
DOCUMENT_TYPES.map(d => [d.id, emptyDocState()]),
) as DocsState
}
export function readResultsFromStorage(): unknown | null {
if (typeof window === 'undefined') return null
try {
const s = localStorage.getItem(STORAGE_KEY_RESULTS)
return s ? JSON.parse(s) : null
} catch { return null }
}
export function readHistoryFromStorage(): HistoryEntry[] {
if (typeof window === 'undefined') return []
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY_HISTORY) || '[]')
} catch { return [] }
}
export function readActiveCheckId(): string {
if (typeof window === 'undefined') return ''
return localStorage.getItem(STORAGE_KEY_CHECK_ID) || ''
}
export function countWords(text: string): number {
if (!text.trim()) return 0
return text.trim().split(/\s+/).length
}
@@ -0,0 +1,83 @@
/**
* Custom hook: resume-polling für eine laufende Compliance-Check-Pruefung.
*
* Beim Mount: wenn localStorage eine `STORAGE_KEY_CHECK_ID` enthaelt aber
* noch kein Result da ist, pollt der Hook alle 3s den Status. Setzt
* Result, Progress, Error oder cleared den active-check-id beim
* Abschluss.
*/
import { useEffect } from 'react'
import {
STORAGE_KEY_CHECK_ID, STORAGE_KEY_RESULTS,
} from './_compliance_storage'
interface ResumePollingArgs {
activeCheckId: string
results: unknown | null
setLoading: (b: boolean) => void
setProgress: (s: string) => void
setProgressPct: (n: number) => void
setResults: (r: unknown) => void
setActiveCheckId: (s: string) => void
setError: (s: string | null) => void
}
export function useCompliancePollingResume({
activeCheckId, results, setLoading, setProgress, setProgressPct,
setResults, setActiveCheckId, setError,
}: ResumePollingArgs) {
useEffect(() => {
if (!activeCheckId || results) return
let cancelled = false
setLoading(true)
setProgress('Pruefung laeuft noch...')
const poll = async () => {
while (!cancelled) {
await new Promise(r => setTimeout(r, 3000))
try {
const res = await fetch(
`/api/sdk/v1/agent/compliance-check?check_id=${activeCheckId}`,
)
if (!res.ok) continue
const data = await res.json()
if (data.progress) setProgress(data.progress)
if (typeof data.progress_pct === 'number') {
setProgressPct(data.progress_pct)
}
if (data.status === 'completed' && data.result) {
setResults(data.result)
setProgress('')
setProgressPct(0)
setLoading(false)
localStorage.setItem(
STORAGE_KEY_RESULTS, JSON.stringify(data.result),
)
localStorage.removeItem(STORAGE_KEY_CHECK_ID)
setActiveCheckId('')
return
}
if (['failed', 'not_found', 'skipped_tdm'].includes(data.status)) {
if (data.status !== 'not_found') {
setError(
data.error
|| (data.status === 'skipped_tdm'
? 'TDM-Vorbehalt erkannt — Crawl uebersprungen'
: 'Pruefung fehlgeschlagen'),
)
}
setProgress('')
setProgressPct(0)
setLoading(false)
localStorage.removeItem(STORAGE_KEY_CHECK_ID)
setActiveCheckId('')
return
}
} catch { /* retry */ }
}
}
poll()
return () => { cancelled = true }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}