Komplettes Rework der AI-Studio-Vorlage zu einem produktiven Werkzeug fuer
deutsche AcroForm-Formulare (Reha-Antraege, Arzt-Befundberichte):
- Backend: Express spawnt headless Claude CLI ('claude -p --output-format json'
via stdin-Pipe). Prompt enthaelt die Feldnamen als Ziel-Schema plus die
Arbeitsregeln (Stichwortstil, feste Zeichen-Kaestchen ohne Leerzeichen,
Vordrucke respektieren, keine geratenen Werte, nur medizinisch).
- PDF-Handling: pdfjs-dist statt pdf-lib — pdf-lib scheitert an verschluesselten
Object-Streams in DRV-Formularen. annotationStorage + saveDocument, kein
Flatten. Worker-Patch zur Laufzeit forciert Auto-Size und schwarze Schrift.
- Multi-Source-Upload: beliebig viele PDFs/Bilder + optional Freitext.
- Design: Kanagawa Design System (Preset aus ../kanagawa-design-system),
Tailwind lokal gebaut statt CDN, Dark/Light-Toggle, Progress-Indicator.
- Deployment: Multi-Stage-Dockerfile, docker-compose in matrix_default-Netz,
Claude-Credentials vom Host per Volume. PLAN.md + AGENTS.md (Alex-Schema).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6.2 KiB
6.2 KiB
AGENTS.md - Rentenversicherer Repository Guidelines
Overview
Rentenversicherer ist ein lokales Werkzeug zum halb-automatischen
Ausfüllen von AcroForm-PDFs. Browser-UI + lokaler Node-Server. Der
Server ruft die Claude Code CLI (claude -p) als Subprozess auf, um
Daten aus einem Quelldokument in die AcroForm-Feldnamen eines Ziel-PDFs
zu mappen. Das ausgefüllte PDF bleibt editierbar (kein Flatten).
Run / Dev Commands
Requires: Node.js 20+, Claude Code CLI im PATH, gültiges Claude-Code-Login.
# Install
npm install
# Development — Vite (5173) + Server (3001) parallel
npm run dev
# Production build und starten
npm run build
npm start
# Typecheck
npx tsc --noEmit
Lint / Test / Validation
Kein dediziertes Test-Framework. Validation:
# Typecheck (Client + Server, shared tsconfig)
npx tsc --noEmit
# Claude CLI verfügbar?
claude --version
# Smoke-Test (manuell):
# 1. npm run dev
# 2. http://localhost:5173
# 3. Beispiel-PDF hochladen (G2210-11_Aerztlicher_Befundbericht_Anforderung_WAG.pdf)
# 4. Quelldokument hochladen
# 5. Download
# 6. PDF in Acrobat öffnen → Feld klicken → editieren möglich?
Repo Layout
Rentenversicherer/
├── App.tsx # React-Einstieg, State-Machine (IDLE/PROCESSING/REVIEW)
├── components/
│ ├── FileUpload.tsx # Drag & Drop + base64-Encoding
│ └── ReviewPanel.tsx # List-View für extrahierte Felder + PDF-Preview
├── services/
│ ├── api.ts # fetch('/api/process'), multipart/form-data
│ └── pdfService.ts # pdfjs-dist: Widgets lesen + saveDocument
├── server/
│ ├── index.ts # Express + multer, POST /api/process
│ └── claudeRunner.ts # spawn('claude', ['-p', ...]), JSON-Parsing
├── types.ts # ExtractedField, FormResponse, AppStatus, FileData
├── vite.config.ts
├── tsconfig.json
├── package.json
└── PLAN.md
Code Style - TypeScript / React
Komponenten:
- Functional Components mit Hooks; keine Class-Components.
- Props-Interfaces am Anfang der Datei, Name endet auf
Props. - Dateinamen:
PascalCase.tsxfür Komponenten,camelCase.tsfür Services.
Imports:
- React-Imports zuerst, dann 3rd-Party, dann relativ.
- Keine
*.tsx-/*.ts-Extensions in Imports.
Styling:
- Tailwind ausschließlich. Keine CSS-Module, kein inline-style außer für dynamisch berechnete Werte.
- Lucide-Icons (
lucide-react) für alle Icons.
Types:
- Shared Types in
types.ts. Keine Duplikate in Komponenten. anyvermeiden.unknown+ Type-Guards bevorzugen.
Code Style - Server (Node/TypeScript)
- Module: ESM (
"type": "module"in package.json). tsx watchfür dev; kein ts-node, kein nodemon.- Async/await überall, kein callback-style.
- Fehler aus dem Claude-Subprozess als 502 ans Frontend weiterreichen,
mit
messageim JSON-Body — niemals stdout/stderr roh leaken.
Naming Conventions
- Komponenten:
PascalCase(ReviewPanel,FileUpload). - Services:
camelCase(pdfService,claudeRunner,api). - Types/Interfaces:
PascalCase(ExtractedField,FormResponse). - Enums:
PascalCaseKey,UPPER_CASEValues (AppStatus.IDLE). - Routes:
kebab-case(/api/process).
Security Rules
- Kein API-Key im Code und nicht in
.env.local. Auth läuft über das bestehende Claude-Code-Login. - Uploads nur in
os.tmpdir(), pro Request eigenes Subverzeichnis, Cleanup imfinally-Block. multermitlimits.fileSize(z.B. 20 MB) — keine unbegrenzten Uploads.multermit Allowlist für MIME-Types (PDF, PNG, JPEG, WEBP).- Server lauscht nur auf
127.0.0.1, nicht auf0.0.0.0. - Dateinamen aus dem Upload niemals als Pfad verwenden — immer UUID/Counter.
Architecture Rules (non-negotiable)
- Nie flatten — bricht das Kernversprechen, dass das PDF
im Reader editierbar bleibt. pdfjs'
saveDocument()flacht nicht, das ist genau der richtige Modus. - Werte immer über
annotationStorage.setValue(widgetId, { value })setzen, nicht versuchen einzelne Annotation-Objekte zu mutieren. - Original-PDF-Bytes bleiben strukturell unverändert. Nur Feldwerte werden gesetzt. Keine Seiten-Mutationen, keine neuen Objekte, keine Annotations.
- Wenn Ziel-PDF keine AcroForm-Felder hat: Hard-Fail mit klarer User-Meldung. Kein Fallback auf Overlay/Koordinaten-Modus.
claude-Subprozess immer mit--permission-mode bypassPermissions, sonst blockiert er auf Tool-Prompts.- Der Server gibt nur strukturiertes JSON an das Frontend weiter — Claude-Stdout nie ungeparst durchreichen.
- Temp-Verzeichnisse werden nach jedem Request gelöscht, auch im Fehlerfall.
Error Handling
- Client zeigt alle Fehler im roten Banner auf der Upload-Seite.
- Server-Errors: JSON
{ error: string, details?: string }, HTTP-Status 4xx/5xx passend. - Claude-CLI-Exitcode ≠ 0 oder Timeout → 502 +
message: "Claude CLI failed"+ Exit-Code. - Kein Retry auf Server-Seite. Bei Bedarf triggert der User den Request erneut.
- Unhandled rejections im Server:
process.on('unhandledRejection', ...)loggen, nicht crashen.
Claude CLI Interface
Der Aufruf aus server/claudeRunner.ts:
claude \
-p "<prompt>" \
--output-format json \
--permission-mode bypassPermissions
Prompt-Grobskizze (Deutsch, weil Formulare deutsch sind):
Du füllst ein deutsches Behördenformular aus.
TARGET-FORM: <tempdir>/form.pdf
SOURCE: <tempdir>/source.pdf
Die Feldnamen im TARGET-Formular sind:
- feld1 (PDFTextField)
- feld2 (PDFCheckBox)
...
Lies beide Dateien mit dem Read-Tool, extrahiere die Werte aus dem
SOURCE und gib NUR ein JSON-Objekt zurück im Format:
{
"summary": "...",
"fields": [
{ "key": "feldname", "label": "...", "value": "...",
"sourceContext": "...",
"validation": { "status": "VALID"|"WARNING"|"INVALID",
"message": "...", "suggestion": "..." } }
]
}
Deutsches Format:
- Datum: DD.MM.YYYY
- Zahlen: Komma als Dezimaltrenner
- Checkbox-Wert: "X" wenn angekreuzt, "" sonst
Das JSON landet als String im result-Feld des CLI-Output-Wrappers
(--output-format json). claudeRunner.ts zieht es raus und
JSON.parset es.