tillandClaude Opus 5 15634f38bb Drucken: Bibliothek als Quelle, Verlauf der Drucke, Mailzugang aus der Umgebung
Drei Dinge, die im Alltag gefehlt haben.

**Bilder aus der Bibliothek in den Druckbereich.** Bisher musste man eine
Datei hochladen, auch wenn Klarbild sie längst hatte. Jetzt gibt es einen
Wähler mit Suche, Ordnerfilter und Mehrfachauswahl — erreichbar aus der
leeren Ablage, aus der Kopfzeile der ersten Karte und an jeder Zellengruppe.

**Verlauf der Drucke.** Gespeichert wird bewusst nur die Anleitung, nicht
das Ergebnis: ein A3-Bogen ist schnell 20 bis 80 MB, ein Verlaufseintrag
rund ein bis drei Kilobyte. Weil das Druckmodul ohne KI rechnet, ergibt
derselbe Auftrag wieder exakt dasselbe Blatt — nur die Zeitstempel im PDF
unterscheiden sich, der Bildinhalt ist Byte für Byte gleich. „Nochmal
drucken" gibt es deshalb nur, wenn alle Zellen aus der Bibliothek kamen und
alle Bilder noch da sind; sonst steht am Eintrag, woran es liegt. Je Konto
bleiben die letzten 100 Drucke, ältere räumt der Eintrag selbst weg.
Sichtbar sind nur die eigenen Läufe, Admins sehen alle.

**Mailzugang aus der Umgebung.** Der Versand nimmt jetzt die Server-
Einstellung, solange unter Admin → E-Mail nichts eingetragen ist. Die
Zugangsdaten liegen als Umgebungsvariablen in Coolify, nicht im Repo. Der
Admin zeigt an, welche Quelle gerade wirklich greift.

Unterwegs mitgenommen:

- Auf schmalen Geräten stand die rechte Spalte oben (order:-1, damit die
  Vorschau gleich sichtbar ist). Mit der Verlaufskarte hätte man an einem
  ganzen Block vorbeigescrollt, ehe man ein Bild hinzufügen kann — die
  Karten laufen jetzt in ihrer eigenen Nummerierung.
- Die Kurzfassung listete jede Zelle einzeln: „10 × 15 cm, 10 × 15 cm".
  Gleiche Maße werden zusammengezogen, vorhandene Stückzahlen addiert.
  Der erste Ansatz machte dabei aus „13 × 18 cm" prompt „13 cm ×18" — das
  × im Maß sah aus wie eine Stückzahl. Sieben Tests halten das jetzt fest.
- „das PDF war 0.0 MB groß" für einen 20-KB-Bogen: unter einem Megabyte
  wird in KB gerechnet.
- Fehlt eine Datei auf der Platte, zeigte der Browser ein kaputtes
  Bildsymbol. Jetzt bleibt die Fläche ruhig.

111 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNhLunz586g6DeYMa7Qsd1
2026-09-01 14:17:15 +00:00

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) oder sticker<N> (sticker5 = 5×5 cm). Parsing in src/lib/format.ts, keine Migration nötig.
  • Dateiformat: PNG (verlustfrei, Transparenz) oder JPG (klein, Picdrop-freundlich). Global über settings.output_ext, pro Rezept via recipes.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 Zelle oversize = 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. Zeitzone KLARBILD_TZ (Standard Europe/Berlin). Nachbenennung der Bibliothek und erneute Auslieferung im Admin unter „Dateinamen" (src/lib/renames.ts), idempotent.
  • Teilen (/s/<slug>, öffentlich): ein Bild oder eine Sammlung ohne Konto zugänglich machen — ansehen und in voller Auflösung herunterladen. Voreinstellungen bewusst vorsichtig: Ablauf (Standard 30 Tage), widerrufbar, optionales Zugangswort (argon2), Download abschaltbar, noindex. Zugriff hängt am Link, nicht am Bild (fremde item-Kennung ⇒ 404); private Sitzungen lassen sich nie teilen. Verwaltung unter /teilen mit Aufrufen und Downloads. Gezählt wird wann und womit, nicht wer: IP nur gehasht, gesalzen mit Server-Geheimnis und Link-Kennung, eine Zeile je Besucher und Stunde, Löschung nach 90 Tagen. Alle Bilder als ZIP über GET /api/s/<slug>/zip — strömend, eine Datei nach der anderen (440 MB Bilder → 196 MB Spitzenspeicher gemessen), gedeckelt bei 200 Dateien und 500 MB. Kern: src/lib/shares.ts.
  • Weitergeben: per E-Mail aus Klarbild heraus (mehrere Empfänger, kein Anhang — ein Link lässt sich zurückziehen), über das eigene Mailprogramm oder per wa.me-Link, der WhatsApp mit fertigem Text öffnet. (Server-seitiger WhatsApp-Versand bräuchte die Business-API: Meta-Konto, genehmigte Vorlagen, Gebühren je Konversation.)
  • E-Mail (src/lib/mail.ts): SMTP über das eigene Postfach — SPF/DKIM stimmen dadurch von selbst und kein Versanddienst sieht mit. Zugang AES-256-GCM in settings. Trägt Passwort-Reset (Einmal-Token, nur Hash gespeichert, 60 min, gleiche Antwort für unbekannte Adressen), Auftragshinweise (je Nutzer, /konto) und den Versand von Teilen-Links.
  • Sitzungen: signiertes Cookie plus serverseitige Prüfung (session_epoch). Passwortwechsel und Rollenänderung entwerten alle offenen Anmeldungen; die Rolle kommt aus der Datenbank, nicht aus dem Cookie.
  • KI-Kennzeichnung (src/lib/provenance.ts): IPTC DigitalSourceType als XMP direkt in der Datei (trainedAlgorithmicMedia bzw. compositeWithTrainedAlgorithmicMedia) samt Modell — von Hand in PNG-iTXt und JPEG-APP1 geschrieben, ohne neue native Abhängigkeit. Mit exiftool prüfbar.
  • Sicherheits-Header: CSP mit echten Hashes für jedes inline-Skript (experimental.csp), dazu nosniff, referrer-policy, X-Frame-Options, permissions-policy. style-src bleibt offen, weil React Stile als Attribute setzt und Attribute sich nicht hashen lassen.
  • Admin: Key/Picdrop/Modelle/Nutzer/Kostendeckel/Presets/Speicherverwaltung, E-Mail, Teilen-Links, 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), shares/share_items/share_views + settings.smtp_* + users.email/notify_jobs (014), users.session_epoch (015).

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 sticker5 an, den der Resolver nicht kannte → jeder Lauf schlug fehl. Lösung: generisches Parsen von sticker<N>/WxH.
  • Auftrag trotz Fehler grün: jobs.status kannte kein failed; alle Positionen fehlgeschlagen wurde trotzdem done. Lösung: Status failed, wenn done=0 && failed>0 (src/worker.ts maybeComplete).
  • Doppelte Datums-/UUID-Namen: \b-Wortgrenzen greifen nicht an Unterstrichen. Lösung: \b entfernt, Datum/UUID/Hex global ersetzt (src/lib/pipeline.ts cleanMotif).
  • Content-Type bei gemischten PNG/JPG-Ergebnissen: file.ts snifft 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.

S
Description
Klarbild — Screenshots & Fundstuecke zu druckfertigen Bildern (Astro+Postgres+S3)
Readme
2.4 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 86.6%
Astro 11.3%
JavaScript 1.7%
CSS 0.3%
Dockerfile 0.1%