From cb48b8289e56925f53b823e9de3402a0474ccbee Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Wed, 11 Mar 2026 14:58:29 +0100 Subject: [PATCH] fix(sdk): Align scope types with engine output + project isolation + optional block progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type alignment (root cause of client-side crash): - RiskFlag: id/title/description → severity/category/message/recommendation - ScopeGap: id/title/recommendation/relatedDocuments → gapType/currentState/targetState/effort - NextAction: id/priority:number/effortDays → actionType/priority:string/estimatedEffort - ScopeReasoning: details → factors + impact - TriggeredHardTrigger: {rule: HardTriggerRule} → flat fields (ruleId, description, etc.) - All UI components updated to match engine output shape Project isolation: - Scope localStorage key now includes projectId (prevents data leak between projects) Optional block progress: - Blocks with only optional questions now show green checkmark when any question answered Co-Authored-By: Claude Opus 4.6 --- .../app/sdk/compliance-scope/page.tsx | 29 ++++- .../sdk/compliance-scope/ScopeDecisionTab.tsx | 117 +++++++++--------- .../sdk/compliance-scope/ScopeExportTab.tsx | 50 +++++--- .../sdk/compliance-scope/ScopeOverviewTab.tsx | 40 +++--- .../sdk/compliance-scope/ScopeWizardTab.tsx | 9 +- .../lib/sdk/compliance-scope-engine.ts | 2 +- .../lib/sdk/compliance-scope-types.ts | 90 ++++++++------ 7 files changed, 185 insertions(+), 152 deletions(-) diff --git a/admin-compliance/app/sdk/compliance-scope/page.tsx b/admin-compliance/app/sdk/compliance-scope/page.tsx index 4e09ddd..0e31258 100644 --- a/admin-compliance/app/sdk/compliance-scope/page.tsx +++ b/admin-compliance/app/sdk/compliance-scope/page.tsx @@ -37,14 +37,31 @@ const TABS: { id: TabId; label: string; icon: string }[] = [ export default function ComplianceScopePage() { const { state: sdkState, dispatch } = useSDK() + // Project-specific storage key + const projectStorageKey = sdkState.projectId + ? `${STORAGE_KEY}_${sdkState.projectId}` + : STORAGE_KEY + // Active tab state const [activeTab, setActiveTab] = useState('overview') + // Migrate old decision format: drop decision if it has old-format fields + const migrateState = (state: ComplianceScopeState): ComplianceScopeState => { + if (state.decision) { + const d = state.decision as Record + // Old format had 'level' instead of 'determinedLevel', or docs with 'isMandatory' + if (d.level || !d.determinedLevel) { + return { ...state, decision: null } + } + } + return state + } + // Local scope state const [scopeState, setScopeState] = useState(() => { // Try to load from SDK context first if (sdkState.complianceScope) { - return sdkState.complianceScope + return migrateState(sdkState.complianceScope) } return createEmptyScopeState() }) @@ -68,14 +85,14 @@ export default function ComplianceScopePage() { const ctxScope = sdkState.complianceScope if (ctxScope && ctxScope.answers?.length > 0) { syncingFromSdk.current = true - setScopeState(ctxScope) + setScopeState(migrateState(ctxScope)) setIsLoading(false) } else if (isLoading) { // SDK has no scope data — try localStorage fallback, then give up try { - const stored = localStorage.getItem(STORAGE_KEY) + const stored = localStorage.getItem(projectStorageKey) if (stored) { - const parsed = JSON.parse(stored) as ComplianceScopeState + const parsed = migrateState(JSON.parse(stored) as ComplianceScopeState) if (parsed.answers?.length > 0) { setScopeState(parsed) dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: parsed }) @@ -106,7 +123,7 @@ export default function ComplianceScopePage() { return } try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(scopeState)) + localStorage.setItem(projectStorageKey, JSON.stringify(scopeState)) dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: scopeState }) } catch (error) { console.error('Failed to save compliance scope state:', error) @@ -194,7 +211,7 @@ export default function ComplianceScopePage() { const emptyState = createEmptyScopeState() setScopeState(emptyState) setActiveTab('overview') - localStorage.removeItem(STORAGE_KEY) + localStorage.removeItem(projectStorageKey) }, []) // Calculate completion statistics diff --git a/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx b/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx index 1e00300..564019a 100644 --- a/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx +++ b/admin-compliance/components/sdk/compliance-scope/ScopeDecisionTab.tsx @@ -58,22 +58,23 @@ export function ScopeDecisionTab({ return 'from-green-500 to-green-600' } - const getSeverityBadge = (severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL') => { - const colors = { - LOW: 'bg-gray-100 text-gray-800', - MEDIUM: 'bg-yellow-100 text-yellow-800', - HIGH: 'bg-orange-100 text-orange-800', - CRITICAL: 'bg-red-100 text-red-800', + const getSeverityBadge = (severity: string) => { + const s = severity.toLowerCase() + const colors: Record = { + low: 'bg-gray-100 text-gray-800', + medium: 'bg-yellow-100 text-yellow-800', + high: 'bg-orange-100 text-orange-800', + critical: 'bg-red-100 text-red-800', } - const labels = { - LOW: 'Niedrig', - MEDIUM: 'Mittel', - HIGH: 'Hoch', - CRITICAL: 'Kritisch', + const labels: Record = { + low: 'Niedrig', + medium: 'Mittel', + high: 'Hoch', + critical: 'Kritisch', } return ( - - {labels[severity]} + + {labels[s] || severity} ) } @@ -254,9 +255,9 @@ export function ScopeDecisionTab({ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> - {trigger.rule.description} + {trigger.description} - Min. {trigger.rule.minimumLevel} + Min. {trigger.minimumLevel} {expandedTrigger === idx && (
-

{trigger.explanation}

- {trigger.rule.legalReference && ( +

{trigger.description}

+ {trigger.legalReference && (

- Rechtsgrundlage: {trigger.rule.legalReference} + Rechtsgrundlage: {trigger.legalReference}

)} - {trigger.rule.mandatoryDocuments && trigger.rule.mandatoryDocuments.length > 0 && ( + {trigger.mandatoryDocuments && trigger.mandatoryDocuments.length > 0 && (

- Pflichtdokumente: {trigger.rule.mandatoryDocuments.join(', ')} + Pflichtdokumente: {trigger.mandatoryDocuments.join(', ')}

)} - {trigger.rule.dsfaRequired && ( + {trigger.requiresDSFA && (

DSFA erforderlich

)}
@@ -302,10 +303,10 @@ export function ScopeDecisionTab({ - - + + - + @@ -317,16 +318,16 @@ export function ScopeDecisionTab({ {DOCUMENT_TYPE_LABELS[doc.documentType] || doc.documentType} - {doc.required && ( + {doc.requirement === 'mandatory' && ( Pflicht )} - +
TypTiefeDokumentPriorität AufwandStatusTrigger Aktion
{doc.depth}{doc.priority} - {doc.estimatedEffort || '-'} + {doc.estimatedEffort ? `${doc.estimatedEffort}h` : '-'} {doc.triggeredBy && doc.triggeredBy.length > 0 && ( @@ -361,10 +362,12 @@ export function ScopeDecisionTab({ {decision.riskFlags.map((flag, idx) => (
-

{flag.title}

+

{flag.message}

{getSeverityBadge(flag.severity)}
-

{flag.description}

+ {flag.legalReference && ( +

{flag.legalReference}

+ )}

Empfehlung: {flag.recommendation}

@@ -382,26 +385,19 @@ export function ScopeDecisionTab({ {decision.gaps.map((gap, idx) => (
-

{gap.title}

+

{gap.description}

{getSeverityBadge(gap.severity)}
-

{gap.description}

-

- Empfehlung: {gap.recommendation} +

+ Ist: {gap.currentState}

- {gap.relatedDocuments && gap.relatedDocuments.length > 0 && ( -
- Betroffene Dokumente: - {gap.relatedDocuments.map((doc, docIdx) => ( - - {DOCUMENT_TYPE_LABELS[doc] || doc} - - ))} -
- )} +

+ Soll: {gap.targetState} +

+
+ Aufwand: ~{gap.effort}h + Level: {gap.requiredFor} +
))}
@@ -416,21 +412,21 @@ export function ScopeDecisionTab({ {decision.nextActions.map((action, idx) => (
- {action.priority} + {idx + 1}

{action.title}

{action.description}

- {action.effortDays && ( + {action.estimatedEffort > 0 && ( - Aufwand: {action.effortDays} Tage + Aufwand: ~{action.estimatedEffort}h )} - {action.relatedDocuments && action.relatedDocuments.length > 0 && ( - - Dokumente: {action.relatedDocuments.length} - + {action.sdkStepUrl && ( + + Zum SDK-Schritt → + )}
@@ -469,8 +465,8 @@ export function ScopeDecisionTab({ )}
- {/* Audit Trail */} - {decision.auditTrail && decision.auditTrail.length > 0 && ( + {/* Audit Trail (from reasoning) */} + {decision.reasoning && decision.reasoning.length > 0 && (