Five things off the open list in the wiki. CSP with real hashes for every inline script (Astro's experimental.csp), so no 'unsafe-inline' for scripts. style-src is deliberately reopened in middleware: React sets styles as attributes and attributes cannot be hashed -- measured, the hash-only policy blocks them and the print preview loses its layout. Plus nosniff, referrer-policy, X-Frame-Options and a permissions-policy. Verified across all pages: zero violations. mem_limit 3g in compose. sharp can spike; without a limit an outlier takes the whole container down instead of just the process, and the healthcheck can't help. AI results now carry IPTC DigitalSourceType (trainedAlgorithmicMedia, or compositeWithTrainedAlgorithmicMedia for edited photos) plus the model, embedded as XMP -- written by hand into PNG iTXt and JPEG APP1 rather than pulling in a sharp upgrade with a native postinstall. exiftool reads it back from both formats; pixels, dimensions and dpi are provably untouched. Crop maths lived twice, in the browser preview and the server renderer, with nothing holding them together. Now one dependency-free module, and 60000 randomised cases assert both entry points agree. That test immediately found a real bug: the zoom limits (0.02 / 8x) also clamped the *initial* crop, so an extreme format silently broke the aspect ratio -- "Rand lassen" would have cropped after all. Limits are now widened to whatever the format needs. Also: the colour flag sat exactly on the selection circle in the library and covered it. Moved to the bottom left. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BNhLunz586g6DeYMa7Qsd1
Klarbild
Verwandelt Screenshots und Fundstücke in saubere, druckfertige Bilder — Rahmen und Shop-Oberflächen entfernen, exakte Fotoformate (30×40 cm etc.), The-Frame-Querformat, für den Cricut freistellen, und automatisch nach Picdrop ausliefern.
Claim: „Screenshot rein, sauberes Bild raus."
Stack
Astro 5 (SSR, Node standalone) + React-Inseln · Postgres · S3 (MinIO) · pg-boss · sharp · pdf-lib (Druckbogen) · grammY (Telegram) · OKLCH-Tokens, kein Tailwind. Deploy: Gitea → Coolify → Hetzner.
Entwicklung
npm install
cp .env.example .env # Werte eintragen
npm run dev
Migrationen und Seeds laufen beim Serverstart automatisch. Erststart-Passwörter über
SEED_TILL_PASSWORD / SEED_LEA_PASSWORD.
Funktionen (Kurzüberblick)
- Modi: Bereinigen (Screenshot säubern/freistellen/formatieren), Umwandeln (ein Bild + Text, z. B. Foto → Ölgemälde), Kombinieren (mehrere Bilder + Text), Neu erzeugen (nur Text).
- Formate: feste cm-/DIN-/Screen-Formate und frei definierbare — Schlüssel
WxH(z. B.25x35) odersticker<N>(sticker5= 5×5 cm). Parsing insrc/lib/format.ts, keine Migration nötig. - Dateiformat: PNG (verlustfrei, Transparenz) oder JPG (klein, Picdrop-freundlich). Global über
settings.output_ext, pro Rezept viarecipes.output_ext, pro Umwandlung im Studio. JPG nur ohne Alpha. - Auslieferung: Picdrop (SFTP/FTPS) + beliebige Zusatzziele; NAS/Backup-Ziele; optionaler
.md-Metadaten-Beileger pro Ziel schaltbar. Nicht-transparente Ergebnisse gehen als JPG an Picdrop. - Bibliothek: Ordner, Farbmarkierungen, Prompt-Anzeige, Vorher/Nachher, Vollbild + „In Fotos sichern".
- Rechte/Datenschutz: Sichtbarkeit (eigene/alle), anonyme Generierungen, private Sessions, NSFW-Gate.
- Telegram-Bot (grammY-Webhook) als vollwertiger Website-Ersatz. API-Token (
Bearer) für/api/*. - Drucken (
/drucken, ohne KI): Bilder exakt auf physische Maße bringen (interaktiver Zuschnitt mit Zoom/Verschieben) und mehrere davon in 100-%-Größe mit Schnittmarken auf einen Bogen setzen — Passbildsatz, Kita-Satz, Sticker. Papier DIN A6–A2 inkl. A3+, Fotopapier 9×13–20×30, Letter/Legal oder freies Maß. Marken: Eckmarken (0,25 pt, außerhalb des Endformats) oder durchgehende Linien. Beschnittzugabe 0–10 mm. Ausgabe: PDF in 1:1 (pdf-lib) bzw. Einzelbild mit dpi-Metadaten. Layout: gleiche Größen → exaktes Raster, gemischte Größen → MaxRects-Packer. Alle Formate der KI-Generierung sind auch hier wählbar; passt das Bild nicht zum Format, entscheidet man je Bild zwischen Zuschneiden und Rand lassen (mit Randfarbe) — nie verzerren. 86 Bildmaße in 10 Gruppen (Passbild bis 100 × 140 cm), 19 Papierformate. Größer als der Bogen (20 × 30 cm auf A4): je Zelleoversize=fit(melden) ·overflow(mittig aufs ganze Blatt, Verlust wird vorab beziffert — hier genau 3 mm) ·tile(Posterdruck auf mehrere Blätter mit gestricheltem Klebefalz und Blattnummer). Beschriftung frei: aus, automatisch oder eigener Text an einer von sechs Stellen; sie entfällt automatisch, wenn an der Kante kein Platz frei ist. Dasselbe Bild darf mehrfach als Zelle vorkommen („Duplizieren"), um es in zwei Maßen zu drucken. Kern:src/lib/paper.ts(Formate),src/lib/printlayout.ts(reine Mathematik, getestet),src/lib/printoversize.ts(Überstand & Kachelung, getestet),src/lib/printrender.ts(sharp + pdf-lib). Browser-Vorschau und Server-PDF benutzen denselben Rechenkern. Auch per Telegram („🖨 Drucken") und MCP (exact_size,print_sheet). Öffentliche Vorstellungsseite ohne Login:/vorstellung. - Dateinamen (
src/lib/naming.ts, abhängigkeitsfrei):JJJJ-MM-TT_HHMMSS_<motiv>_<format>_<kürzel>.<ext>, z. B.2026-08-20_143207_lea-badge_the-frame_a3f9.jpg. Zeitstempel vorn → jede Dateiliste sortiert chronologisch; Kürzel aus der Item-ID → zwei Bilder können sich beim Ausliefern nicht mehr überschreiben. ZeitzoneKLARBILD_TZ(StandardEurope/Berlin). Nachbenennung der Bibliothek und erneute Auslieferung im Admin unter „Dateinamen" (src/lib/renames.ts), idempotent. - Admin: Key/Picdrop/Modelle/Nutzer/Kostendeckel/Presets/Speicherverwaltung, Dateinamen, Picdrop-Diagnose.
Datenmodell / Migrationen
Nummerierte SQL in migrations/, beim Start automatisch (kein ORM-Push). Wichtige Spalten:
items.has_alpha, jobs.status (inkl. failed), jobs.finished_at,
settings.output_ext / recipes.output_ext, settings.*_metadata_sidecar, delivery_targets.*,
print_presets (Bogen-Vorlagen, 013).
Tests
npm test — Node-Testrunner mit nativem Type-Stripping (node --experimental-strip-types).
Kein tsx, kein playwright in package.json — der Docker-Build installiert devDependencies,
und deren native postinstall-Schritte haben den Deploy schon zweimal zerlegt. Testwerkzeug gehört
außerhalb des Projekts installiert. Deckt die Druck-Layoutmathematik ab (Maß-Parsing,
mm → px exakt, Überlappungsfreiheit, Ränder, Beschnittzugabe, Markengeometrie, Mehrseitigkeit).
Stolpersteine & Lösungen (Learnings)
- „Bild bei Picdrop ✓, aber nicht in der Galerie": Fehldiagnose vermeiden — die Dateien lagen die ganze
Zeit korrekt per SFTP in
/POSTER LEA(per Admin → Picdrop → „Ordner anzeigen" verifizierbar). Der eigentliche Grund war die Sortierung in der Picdrop-Weboberfläche, nicht das Format. Unabhängig davon liefern wir nicht-transparente Ergebnisse jetzt als JPG aus (src/lib/delivery.ts,deliverableBuffer): ~25 MB PNG → ~2 MB JPG, schneller und handlicher — plus wählbares Ausgabeformat (output_ext). - „Unbekanntes Format: sticker5": Seed legte ein Rezept mit Formatschlüssel
sticker5an, den der Resolver nicht kannte → jeder Lauf schlug fehl. Lösung: generisches Parsen vonsticker<N>/WxH. - Auftrag trotz Fehler grün:
jobs.statuskannte keinfailed; alle Positionen fehlgeschlagen wurde trotzdemdone. Lösung: Statusfailed, wenndone=0 && failed>0(src/worker.tsmaybeComplete). - Doppelte Datums-/UUID-Namen:
\b-Wortgrenzen greifen nicht an Unterstrichen. Lösung:\bentfernt, Datum/UUID/Hex global ersetzt (src/lib/pipeline.tscleanMotif). - Content-Type bei gemischten PNG/JPG-Ergebnissen:
file.tssnifft jetzt die Magic Bytes. .tmp--Reste auf Picdrop: früher Temp-Rename-Bug;cleanupTmp+ Admin-Knopf „Reste aufräumen".
Status
Reihenfolge und Nachweise siehe CLAUDE.md. Anforderungen im Handover-Paket
(Imagetool-Lea/00..07). Führend: 01-anforderungen.md.