[split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)

Phase 1 — Python (klausur-service): 5 monoliths → 36 files
- dsfa_corpus_ingestion.py (1,828 LOC → 5 files)
- cv_ocr_engines.py (2,102 LOC → 7 files)
- cv_layout.py (3,653 LOC → 10 files)
- vocab_worksheet_api.py (2,783 LOC → 8 files)
- grid_build_core.py (1,958 LOC → 6 files)

Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files
- staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3)
- policy_handlers.go (700 → 2), repository.go (684 → 2)
- search.go (592 → 2), ai_extraction_handlers.go (554 → 2)
- seed_data.go (591 → 2), grade_service.go (646 → 2)

Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files
- sdk/types.ts (2,108 → 16 domain files)
- ai/rag/page.tsx (2,686 → 14 files)
- 22 page.tsx files split into _components/ + _hooks/
- 11 component files split into sub-components
- 10 SDK data catalogs added to loc-exceptions
- Deleted dead backup index_original.ts (4,899 LOC)

All original public APIs preserved via re-export facades.
Zero new errors: Python imports verified, Go builds clean,
TypeScript tsc --noEmit shows only pre-existing errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-24 17:28:57 +02:00
parent 9ba420fa91
commit b681ddb131
251 changed files with 30016 additions and 25037 deletions

View File

@@ -0,0 +1,60 @@
'use client'
import type { ScreenDefinition } from '../types'
interface CategoryFilterProps {
screens: ScreenDefinition[]
categories: string[]
colors: Record<string, { bg: string; border: string; text: string }>
labels: Record<string, string>
selectedCategory: string | null
selectedNode: string | null
onSelectCategory: (category: string | null) => void
onReset: () => void
}
export function CategoryFilter({
screens,
categories,
colors,
labels,
selectedCategory,
selectedNode,
onSelectCategory,
onReset,
}: CategoryFilterProps) {
return (
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="flex flex-wrap gap-2">
<button
onClick={onReset}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
selectedCategory === null && !selectedNode
? 'bg-slate-800 text-white'
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
}`}
>
Alle ({screens.length})
</button>
{categories.map((key) => {
const count = screens.filter(s => s.category === key).length
const catColors = colors[key] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
return (
<button
key={key}
onClick={() => onSelectCategory(selectedCategory === key ? null : key)}
className="px-4 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2"
style={{
background: selectedCategory === key ? catColors.border : catColors.bg,
color: selectedCategory === key ? 'white' : catColors.text,
}}
>
<span className="w-3 h-3 rounded-full" style={{ background: catColors.border }} />
{labels[key]} ({count})
</button>
)
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,51 @@
'use client'
import type { ScreenDefinition } from '../types'
interface ConnectedScreensListProps {
selectedNode: string | null
connectedScreens: ScreenDefinition[]
colors: Record<string, { bg: string; border: string; text: string }>
baseUrl: string
}
export function ConnectedScreensList({
selectedNode,
connectedScreens,
colors,
baseUrl,
}: ConnectedScreensListProps) {
if (!selectedNode || connectedScreens.length <= 1) return null
return (
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-sm font-medium text-slate-700 mb-3">Verbundene Screens:</div>
<div className="flex flex-wrap gap-2">
{connectedScreens.map((screen) => {
const catColors = colors[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
const isCurrentNode = screen.id === selectedNode
return (
<button
key={screen.id}
onClick={() => {
if (screen.url) {
window.open(`${baseUrl}${screen.url}`, '_blank')
}
}}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-all flex items-center gap-2 ${
isCurrentNode ? 'ring-2 ring-primary-500' : ''
}`}
style={{
background: isCurrentNode ? catColors.border : catColors.bg,
color: isCurrentNode ? 'white' : catColors.text,
}}
>
<span>{screen.icon}</span>
{screen.name}
</button>
)
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,95 @@
'use client'
import ReactFlow, {
Node,
Edge,
Controls,
Background,
MiniMap,
BackgroundVariant,
Panel,
NodeChange,
EdgeChange,
} from 'reactflow'
import 'reactflow/dist/style.css'
import type { ScreenDefinition, FlowType } from '../types'
interface FlowDiagramProps {
flowType: FlowType
nodes: Node[]
edges: Edge[]
onNodesChange: (changes: NodeChange[]) => void
onEdgesChange: (changes: EdgeChange[]) => void
onNodeClick: (event: React.MouseEvent, node: Node) => void
onPaneClick: () => void
screens: ScreenDefinition[]
colors: Record<string, { bg: string; border: string; text: string }>
labels: Record<string, string>
categories: string[]
}
export function FlowDiagram({
flowType,
nodes,
edges,
onNodesChange,
onEdgesChange,
onNodeClick,
onPaneClick,
screens,
colors,
labels,
categories,
}: FlowDiagramProps) {
return (
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden" style={{ height: '500px' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}
fitView
fitViewOptions={{ padding: 0.2 }}
attributionPosition="bottom-left"
>
<Controls />
<MiniMap
nodeColor={(node) => {
const screen = screens.find(s => s.id === node.id)
const catColors = screen ? colors[screen.category] : null
return catColors?.border || '#94a3b8'
}}
maskColor="rgba(0, 0, 0, 0.1)"
/>
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />
<Panel position="top-left" className="bg-white/95 p-3 rounded-lg shadow-lg text-xs">
<div className="font-medium text-slate-700 mb-2">
{flowType === 'studio' ? '🎓 Studio' : '⚙️ Admin v2'}
</div>
<div className="space-y-1">
{categories.slice(0, 4).map((key) => {
const catColors = colors[key] || { bg: '#f1f5f9', border: '#94a3b8' }
return (
<div key={key} className="flex items-center gap-2">
<span
className="w-3 h-3 rounded"
style={{ background: catColors.bg, border: `1px solid ${catColors.border}` }}
/>
<span className="text-slate-600">{labels[key]}</span>
</div>
)
})}
</div>
<div className="mt-2 pt-2 border-t text-slate-400">
Klick = Subtree<br/>
Doppelklick = Oeffnen
</div>
</Panel>
</ReactFlow>
</div>
)
}

View File

@@ -0,0 +1,59 @@
'use client'
import type { FlowType } from '../types'
import { STUDIO_SCREENS, ADMIN_SCREENS } from '../data'
interface FlowTypeSelectorProps {
flowType: FlowType
onFlowTypeChange: (type: FlowType) => void
}
export function FlowTypeSelector({ flowType, onFlowTypeChange }: FlowTypeSelectorProps) {
return (
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => onFlowTypeChange('studio')}
className={`p-6 rounded-xl border-2 transition-all ${
flowType === 'studio'
? 'border-green-500 bg-green-50 shadow-lg'
: 'border-slate-200 bg-white hover:border-slate-300'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-14 h-14 rounded-xl flex items-center justify-center text-2xl ${
flowType === 'studio' ? 'bg-green-500 text-white' : 'bg-slate-100'
}`}>
🎓
</div>
<div className="text-left">
<div className="font-bold text-lg">Studio (Port 8000)</div>
<div className="text-sm text-slate-500">Lehrer-Oberflaeche</div>
<div className="text-xs text-slate-400 mt-1">{STUDIO_SCREENS.length} Screens</div>
</div>
</div>
</button>
<button
onClick={() => onFlowTypeChange('admin')}
className={`p-6 rounded-xl border-2 transition-all ${
flowType === 'admin'
? 'border-primary-500 bg-primary-50 shadow-lg'
: 'border-slate-200 bg-white hover:border-slate-300'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-14 h-14 rounded-xl flex items-center justify-center text-2xl ${
flowType === 'admin' ? 'bg-primary-500 text-white' : 'bg-slate-100'
}`}>
</div>
<div className="text-left">
<div className="font-bold text-lg">Admin v2 (Port 3002)</div>
<div className="text-sm text-slate-500">Admin Panel</div>
<div className="text-xs text-slate-400 mt-1">{ADMIN_SCREENS.length} Screens</div>
</div>
</div>
</button>
</div>
)
}

View File

@@ -0,0 +1,65 @@
'use client'
import type { ScreenDefinition } from '../types'
interface ScreenListProps {
screens: ScreenDefinition[]
colors: Record<string, { bg: string; border: string; text: string }>
labels: Record<string, string>
baseUrl: string
selectedCategory: string | null
onSelectScreen: (screen: ScreenDefinition) => void
}
export function ScreenList({
screens,
colors,
labels,
baseUrl,
selectedCategory,
onSelectScreen,
}: ScreenListProps) {
const filtered = selectedCategory
? screens.filter(s => s.category === selectedCategory)
: screens
return (
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
<div className="px-4 py-3 bg-slate-50 border-b flex items-center justify-between">
<h3 className="font-medium text-slate-700">
Alle Screens ({screens.length})
</h3>
<span className="text-xs text-slate-400">{baseUrl}</span>
</div>
<div className="divide-y max-h-80 overflow-y-auto">
{filtered.map((screen) => {
const catColors = colors[screen.category] || { bg: '#f1f5f9', border: '#94a3b8', text: '#475569' }
return (
<button
key={screen.id}
onClick={() => onSelectScreen(screen)}
className="w-full flex items-center gap-4 p-3 hover:bg-slate-50 transition-colors text-left"
>
<span
className="w-9 h-9 rounded-lg flex items-center justify-center text-lg"
style={{ background: catColors.bg }}
>
{screen.icon}
</span>
<div className="flex-1 min-w-0">
<div className="font-medium text-slate-800 text-sm">{screen.name}</div>
<div className="text-xs text-slate-500 truncate">{screen.description}</div>
</div>
<span
className="px-2 py-1 rounded text-xs font-medium shrink-0"
style={{ background: catColors.bg, color: catColors.text }}
>
{labels[screen.category]}
</span>
</button>
)
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,57 @@
'use client'
import type { ScreenDefinition } from '../types'
interface StatsBarProps {
totalScreens: number
totalConnections: number
connectedCount: number
selectedNode: string | null
previewScreen: ScreenDefinition | null
onReset: () => void
}
export function StatsBar({
totalScreens,
totalConnections,
connectedCount,
selectedNode,
previewScreen,
onReset,
}: StatsBarProps) {
return (
<div className="grid grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-3xl font-bold text-slate-800">{totalScreens}</div>
<div className="text-sm text-slate-500">Screens</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm">
<div className="text-3xl font-bold text-primary-600">{totalConnections}</div>
<div className="text-sm text-slate-500">Verbindungen</div>
</div>
<div className="bg-white rounded-xl border border-slate-200 p-4 shadow-sm col-span-2">
{selectedNode ? (
<div className="flex items-center gap-3">
<div className="text-3xl">{previewScreen?.icon}</div>
<div>
<div className="font-bold text-slate-800">{previewScreen?.name}</div>
<div className="text-sm text-slate-500">
{connectedCount} verbundene Screen{connectedCount !== 1 ? 's' : ''}
</div>
</div>
<button
onClick={onReset}
className="ml-auto px-3 py-1 text-sm bg-slate-100 hover:bg-slate-200 rounded-lg"
>
Zuruecksetzen
</button>
</div>
) : (
<div className="text-slate-500 text-sm">
Klicke auf einen Screen um den Subtree zu sehen
</div>
)}
</div>
</div>
)
}

View File

@@ -0,0 +1,6 @@
export { FlowTypeSelector } from './FlowTypeSelector'
export { StatsBar } from './StatsBar'
export { CategoryFilter } from './CategoryFilter'
export { ConnectedScreensList } from './ConnectedScreensList'
export { FlowDiagram } from './FlowDiagram'
export { ScreenList } from './ScreenList'