- fix(render): ffmpeg overlay=0:0 -> overlay=0:0:shortest=1 to prevent hang on finite PNG sequences - feat(ws): add core/websocket.py ConnectionManager + Redis Pub/Sub subscriber loop - feat(ws): add /api/ws WebSocket endpoint with JWT query-param auth in main.py - feat(ws): emit render_complete/failed + cad_processing_complete events from step_tasks.py - feat(ws): emit order_status_change events from orders router - feat(ws): add beat_tasks.py broadcast_queue_status task (every 10s via Redis __broadcast__) - feat(frontend): add useWebSocket hook with auto-reconnect (exponential backoff, 25s ping) - feat(frontend): add WebSocketContext + WebSocketProvider wrapping App - refactor(frontend): remove polling from WorkerActivity (was 5s/3s) + OrderDetail (was 5s) - refactor(frontend): reduce polling in Layout (8s->60s) + NotificationCenter (15s->60s) - docs: add ffmpeg shortest=1 + WebSocket JWT auth learnings to LEARNINGS.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
23 KiB
Projekt-Learnings — Schaeffler Automat
Format
Datum | Kategorie | Problem → Lösung
Learnings
2026-01-15 | Architektur | Backend-Port-Konflikt
Problem: FastAPI standardmäßig auf Port 8000 — war auf dem Entwicklungsrechner belegt
Lösung: Port 8888 in docker-compose.yml und Vite-Proxy konfiguriert
Für künftige Projekte: Port früh festlegen und in CLAUDE.md dokumentieren
2026-01-20 | Datenbank | SQLAlchemy trackt key-value-Store-Mutations nicht
Problem: Admin-Einstellungen (system_settings) wurden via ORM gespeichert, Änderungen wurden nicht persistiert
Ursache: SQLAlchemy erkennt keine Mutation an einem bereits geladenen Objekt wenn nur ein Value-Feld geändert wird
Lösung: Direktes SQL UPDATE via op.execute() statt ORM-Mutation in admin.py
Für künftige Projekte: Key-Value-Stores immer mit direktem SQL oder session.execute(update(...)) verwalten
2026-01-25 | Render-Pipeline | Blender ignoriert STEP-Einheiten (mm vs. m)
Problem: STEP-Dateien sind in Millimetern, Blender arbeitet intern in Metern → 50mm-Lager erscheint 50 Meter breit, Kamera framt falsch
Lösung: _scale_mm_to_m(parts) Helper in allen 3 Render-Scripts: part.scale = (0.001, 0.001, 0.001), Transform anwenden
Betroffene Dateien: blender_render.py, still_render.py, turntable_render.py
Für künftige Projekte: Einheiten-Konvertierung direkt nach STL-Import, vor jeder Kamera-Kalkulation
2026-01-28 | Render-Pipeline | Blender 5.0 hat scene.node_tree entfernt
Problem: _setup_bg_compositor() rief scene.node_tree auf (in Blender 5.0 entfernt) → Python-Exception → Blender exitete mit Code 0 → Flamenco markierte Task fälschlicherweise als "completed"
Lösung: _setup_bg_compositor() aus Setup + Render-Script entfernt; bg_color-Kompositing in FFmpeg verschoben (-f lavfi -i color=... + overlay-Filter)
Wichtig: Immer try: main() except SystemExit: raise except Exception: traceback; sys.exit(1) in Blender-Scripts — sonst verschluckt Blender Python-Exceptions
Für künftige Projekte: Nach Blender-Major-Updates alle API-Calls prüfen; Exception-Guard ist Pflicht
2026-02-05 | Material-System | Material-Alias-Lookup-Reihenfolge falsch
Problem: Steel--Stahl war sowohl ein kanonischer Material.name als auch ein Alias für SCHAEFFLER_010101_Steel-Bare. Der Lookup prüfte zuerst den exakten Namen und fand Steel--Stahl — Blender konnte diesen Namen aber nicht in der Library finden
Lösung: Lookup-Reihenfolge in material_service.py umgekehrt: Aliases zuerst, dann exakter Name, dann Pass-through
Für künftige Projekte: Alias-System immer so designen dass Aliases Vorrang haben; nie zwei Lookup-Pfade mit überlappenden Treffern
2026-02-10 | Render-Pipeline | Blender-Template zerstört HDRI/World
Problem: Im Template-Modus (Mode B) wurden trotzdem Auto-Lights und eine neue World erstellt → überschrieb den HDRI aus dem .blend-Template → falsche Beleuchtung
Ursache: Auto-Licht- und World-Setup-Code lief bedingungslos, nicht nur im Mode A
Lösung: In Template-Mode werden Lights, World und Color-Management-Override vollständig übersprungen; nur die Kamera wird ggf. neu berechnet
Betroffene Dateien: still_render.py, turntable_render.py, schaeffler-still.js, schaeffler-turntable.js
2026-02-15 | Celery | Blender-Queue-Flooding durch falsche Concurrency
Problem: Alle Celery-Tasks (schnelle Metadata-Extraktion + langsamer Blender-Render) liefen auf step_processing mit concurrency=8 → 8 Workers schickten gleichzeitig Requests an blender-renderer (der nur 1 gleichzeitig verarbeiten kann) → 7 davon liefen in 300s-Timeout → blockierte die gesamte Queue
Lösung: Pipeline aufgeteilt:
process_step_file(step_processing, concurrency=8): nur schnelle Metadata-Extraktion (<2s), queut dann →render_step_thumbnail(thumbnail_rendering, concurrency=1): Blender-Call, niemals timeout Neuer Service:worker-thumbnailindocker-compose.ymlmit--concurrency=1Für künftige Projekte: HTTP-Services die nur 1 Request gleichzeitig verarbeiten können IMMER auf einer separaten Queue mit concurrency=1 laufen lassen
2026-02-18 | Frontend | Tailwind CSS-Variablen inkompatibel mit opacity-Syntax
Problem: bg-surface/50 oder bg-surface (wenn --color-bg-surface ein Hex-Wert ist) generiert rgb(var(--color-bg-surface) / 0.5) — invalides CSS, weil rgb() keine Hex-Werte als Channel-Input akzeptiert → Hintergrund transparent
Ursache: Tailwind erwartet CSS-Variablen mit RGB-Channel-Format (255 255 255), nicht Hex (#ffffff)
Lösung: Inline-Style verwenden: style={{ backgroundColor: 'var(--color-bg-surface)' }}
Für künftige Projekte: Entweder CSS-Variablen im RGB-Channel-Format definieren, oder konsequent inline styles für variable Farben
2026-02-20 | STL-Cache | Three.js-Renderer nutzte tempfile → kein Download möglich
Problem: Three.js-Renderer konvertierte STEP→STL in ein tempfile und löschte es anschließend → STL-Download-Endpoint fand keine Datei
Ursache: Three.js war ursprünglich nur für Thumbnails gebaut, STL-Cache-Konvention ({stem}_low.stl neben STEP-Datei) wurde nicht implementiert
Lösung: Persistent cache path: step_path.parent / f"{step_path.stem}_low.stl", cache-hit-check vor Konvertierung, kein unlink() mehr
Für künftige Projekte: STL-Cache-Konvention ({step_stem}_{quality}.stl neben STEP-Datei) von Anfang an in allen Renderer-Services einhalten
2026-02-20 | STL-Cache | blender-renderer fehlte /convert-stl Endpoint
Problem: Für Produkte die mit Blender gerendert wurden war kein STL-Cache vorhanden wenn nicht explizit gerendert wurde (blender-renderer renderte + konvertierte in einem Schritt, aber STL wurde nicht persistiert)
Lösung: Neuer /convert-stl Endpoint in blender-renderer/app.py: konvertiert STEP→STL ohne Render, persistiert Cache. Neuer Celery-Task generate_stl_cache auf thumbnail_rendering-Queue. Admin-Funktion "Generate Missing STLs" zum Batch-Nachfüllen
2026-02-22 | Material-System | Fehlender Alias blockiert Material-Replacement
Problem: Produkt F-803422.01.TR2 (SA-2026-00080) renderte ohne Materialersetzung. Material "Stahl v2" war korrekt in der UI gespeichert, aber weder in materials noch in material_aliases vorhanden
Ursache: Alias-Seeding aus Excel deckte nicht alle Varianten der deutschen Materialbezeichnungen ab
Lösung: Alias direkt in DB eingetragen: "Stahl v2" → SCHAEFFLER_010101_Steel-Bare
Für künftige Projekte: Bei Render ohne Materialersetzung immer zuerst resolve_material_map() debuggen und Alias-Tabelle prüfen; Alias-Seeding regelmäßig mit neuen Excel-Varianten erweitern
2026-02-25 | Frontend | canDispatch-Bedingung zu restriktiv
Problem: "Dispatch Renders"-Button war nicht sichtbar obwohl der Auftrag offene Render-Zeilen hatte
Ursache: canDispatch enthielt && hasRetryable — Button erschien nur wenn pending/failed/cancelled-Zeilen vorhanden waren, nicht wenn alle Zeilen "pending" im Erstauftrag
Lösung: hasRetryable-Bedingung entfernt; Button ist immer sichtbar wenn Auftrag im richtigen Status und User privilegiert ist
Für künftige Projekte: Aktions-Buttons nicht zu stark von abgeleiteten Zuständen abhängig machen; lieber im Backend validieren
2026-02-28 | Frontend | MaterialInput-Dropdown ohne Hintergrund
Problem: Dropdown der Material-Suchfeld-Komponente erschien transparent — Text über dem Hintergrund kaum lesbar
Ursache: bg-surface Tailwind-Klasse + CSS-Variable mit Hex-Wert (siehe Learning 2026-02-18)
Lösung: style={{ backgroundColor: 'var(--color-bg-surface)' }} für Dropdown-Container, Group-Header und Sticky-Button
Datei: frontend/src/components/shared/MaterialInput.tsx
2026-03-06 | Refactor | .gitignore core trifft Verzeichnisse
Problem: .gitignore enthielt core als Regel (für core dump files) — Git ignorierte damit auch backend/app/core/ Verzeichnis
Lösung: Regel zu /core umbenannt (Root-relative Regel trifft nur /core Datei, nicht verschachtelte core/-Verzeichnisse)
Für künftige Projekte: Immer Root-relative Pfade (/core) für Dateien im Root-Verzeichnis nutzen
2026-03-06 | Architektur | Blender-HTTP-Service vs. direkter Subprocess
Problem: blender-renderer als Flask/FastAPI HTTP-Microservice war ein Single-Point-of-Failure (max. 1 concurrent Request), kein Scaling möglich, HTTP-Overhead bei jedem Render
Lösung: Render-Worker als Celery-Container (render-worker/) — Blender direkt via subprocess.run ohne HTTP. is_blender_available() prüft BLENDER_BIN env var für Kontext-Detection
Wichtig: step_processor.py erkennt über BLENDER_BIN-Env ob Blender im aktuellen Container verfügbar ist — Backend-Container fallen auf Pillow zurück
Für künftige Projekte: Subprocess-basierter Renderer > HTTP-Microservice für blocking compute tasks
2026-03-06 | Refactor | Bash CWD-Problem durch Hook-Pfad-Auflösung
Problem: Nach cd frontend && npm test in einem Bash-Tool-Call blieb CWD dauerhaft in frontend/. Der Pre-Tool-Use-Hook python3 .claude/hooks/pre_tool_use.py wurde dann relativ zu frontend/ aufgelöst → Datei nicht gefunden → alle Tool-Calls blockiert
Lösung: Symlink frontend/.claude → .claude erstellt: ln -sf $(pwd)/.claude frontend/.claude
Für künftige Projekte: Hooks nie mit relativen Pfaden konfigurieren; absoluten Pfad im Hook-Command verwenden. Außerdem: cd immer in separate Bash-Calls oder mit && am Ende der eigentlichen Command-Chain
2026-03-06 | Multi-Tenancy | PostgreSQL RLS mit current_setting und Null-Safety
Problem: current_setting('app.current_tenant_id') wirft Exception wenn Variable nicht gesetzt → alle Queries schlagen fehl wenn kein Tenant-Context gesetzt ist
Lösung: current_setting('app.current_tenant_id', true) — zweites Argument true macht die Funktion Null-safe: gibt NULL statt Exception zurück wenn Setting nicht gesetzt
Admin-Bypass-Pattern: Separates CREATE POLICY admin_bypass ... USING (current_setting(...) = 'bypass') — setzt app.current_tenant_id = 'bypass' für Admin-Cross-Tenant-Queries
Für künftige Projekte: IMMER das zweite true-Argument verwenden; Policies immer testen mit (a) gesetztem Tenant, (b) nicht gesetztem Setting, (c) Admin-Bypass
2026-03-06 | Refactor | Domain-Driven Migration: Compat-Shims statt Big-Bang
Problem: Vollständige Migration aller Models/Services/Router in neue Domain-Struktur in einem Schritt → alle bestehenden Imports brechen
Lösung: Compat-Shims-Ansatz: alte Dateien (app/models/user.py etc.) werden zu Re-Export-Wrappern die aus den neuen Domain-Locations importieren. So funktionieren alle bestehenden Imports weiter während die kanonische Location die neue Domain ist
Pattern:
# app/models/user.py (Compat-Shim)
from app.domains.auth.models import User
__all__ = ["User"]
Für künftige Projekte: Immer Compat-Shims anlegen vor dem Verschieben; erst nach vollständiger Migration aller Imports die Shims entfernen
2026-03-06 | Workflow-System | Celery Canvas vs. Custom Workflow-Engine
Problem: Custom Workflow-Engine (Graph-Traversal, Dependency-Resolution, Retry-Logic) war zu komplex (~2-3 Wochen Eigenentwicklung)
Lösung: Celery Canvas als Execution-Engine (chain, group, chord). dispatch_workflow(type, order_line_id, params) baut den Canvas dynamisch aus Config-Typ. Backward-Compat: wenn kein workflow_definition_id → alter direkter Task-Call
Seeded Workflows: 3 Standard-Definitionen beim Migration-Upgrade direkt in DB geSEEDed (Still, Turntable, Multi-Angle)
Für künftige Projekte: Celery Canvas ist ausreichend für parallele/sequentielle Workflow-Execution; keine eigene Workflow-Engine bauen
2026-03-06 | Circular Import | template_service ↔ domains/rendering/service — Render nie ausgeführt
Problem: app.services.template_service war ein Shim der app.domains.rendering.service importiert. app.domains.rendering.service importierte wiederum app.services.template_service → zirkulärer Import → resolve_template konnte nie geladen werden → jeder Render schlug fehl mit "cannot import name 'resolve_template' from partially initialized module".
Ursache: B1-Refactor hat beide Module zu Shims gemacht die aufeinander zeigen. Die eigentliche Implementierung wurde nicht in die neue Domäne übertragen.
Lösung: template_service.py mit der Originalimplementierung aus dem git-Log wiederhergestellt (sync SQLAlchemy, Celery-sicher, 4-stufige Cascade). domains/rendering/service.py importiert jetzt korrekt aus template_service ohne Rückimport.
Für künftige Projekte: Nach Refactoring immer prüfen ob Shims auf die echte Implementierung zeigen oder wieder auf andere Shims. grep -rn "def resolve_template" vor dem Commit muss mindestens 1 Treffer liefern.
2026-03-06 | Multi-Tenancy | audit_log.tenant_id NOT NULL blockiert alle Notifications
Problem: Migration 036 machte audit_log.tenant_id NOT NULL, aber emit_notification setzt kein tenant_id. Die Notification-Insert schlug fehl → rollback → nachfolgende Session-Zugriffe schlugen fehl → Order-Submit gab 500 zurück.
Lösung: audit_log.tenant_id via ALTER TABLE audit_log ALTER COLUMN tenant_id DROP NOT NULL nullable gemacht. Broadcast-Notifications (system-weit, kein konkreter Tenant) DÜRFEN NULL tenant_id haben.
Für künftige Projekte: Audit-Logs die als Broadcast an alle Tenants gehen benötigen nullable tenant_id. Nie NOT NULL auf Tabellen setzen die auch System-Events speichern.
2026-03-06 | Frontend | GET /api/tenants gibt 307 Redirect zurück
Problem: FastAPI router registriert /tenants/ (mit trailing slash). GET /tenants → 307 Redirect zu /tenants/. Axios folgt dem Redirect aber verliert den Authorization-Header → 401 → leere Tenant-Liste im Frontend.
Lösung: getTenants() in api/tenants.ts auf /tenants/ (mit trailing slash) geändert.
Für künftige Projekte: FastAPI APIRouter mit prefix="/tenants" und @router.get("") erzeugt /tenants (kein Slash). Mit @router.get("/") erzeugt /tenants/. Axios folgt 307 nicht mit Auth-Header. Immer trailing slash im Frontend verwenden wenn Router mit Slash registriert.
Offene Fragen
- Azure AI Credentials für Phase 4 (Bildvalidierung) noch nicht konfiguriert
- pythonOCC verfügbar im render-worker (via cadquery dependency)? Deployment-Test ausstehend
- @xyflow/react noch nicht installiert — npm install nötig nach nächstem
docker compose up --build frontend - Material-Alias-Seeding deckt noch nicht alle deutschen Materialbezeichnungs-Varianten ab
- Turntable-Animation: bg_color via FFmpeg-Overlay — Qualität bei Transparenz-Edges prüfen
2026-03-06 | Docker | apt-Paketname libgdk-pixbuf2.0-0 vs libgdk-pixbuf-2.0-0
WeasyPrint benötigt libgdk-pixbuf. Auf Debian bookworm (python:3.11-slim) heißt das Paket libgdk-pixbuf-2.0-0 (mit Bindestrichen), nicht libgdk-pixbuf2.0-0. apt-get install schlägt mit exit code 100 fehl wenn der Name falsch ist.
→ Immer apt-cache search libgdk im Container prüfen bevor man Paketnamen in Dockerfiles schreibt.
2026-03-06 | Celery | thumbnail_rendering Queue braucht eigenen worker-thumbnail Service
Blender-Renderer verarbeitet nur 1 Request gleichzeitig. Wenn worker (concurrency=8) Tasks auf thumbnail_rendering queued, laufen 7 davon in Timeout (300s). Lösung: separaten worker-thumbnail Service mit --concurrency=1 und -Q thumbnail_rendering in docker-compose.yml. step_processing bleibt bei concurrency=8.
2026-03-06 | Alembic | Migration exit code 100 bei enum-Konflikt
SQLAlchemy Enum(create_type=False) funktioniert nicht zuverlässig mit asyncpg. Bei bereits existierenden PostgreSQL-Enum-Typen: Raw SQL mit DO $$ BEGIN CREATE TYPE ...; EXCEPTION WHEN duplicate_object THEN NULL; END $$; verwenden. Für Tabellen: CREATE TABLE IF NOT EXISTS.
2026-03-06 | Render-Pipeline | Circular Shim blockiert alle Order-Renders
Problem: dispatch_order_line_render → dispatch_render (Shim A→B→A Circular Import) → Render startet nie. Die einzige funktionierende Render-Implementierung render_order_line_task war nie aus dem Dispatch-Chain erreichbar.
Lösung: dispatch_order_line_render direkt auf render_order_line_task.delay() umleiten. render_dispatcher.py-Shim ebenfalls repariert. Dispatch-Service _legacy_dispatch ebenfalls auf render_order_line_task umgeleitet.
Erkenntnisse: Bei Refactoring immer prüfen ob Shims zirkulär werden. Wenn zwei Module sich gegenseitig importieren (A→B und B→A), entsteht ein Circular Import — keine echte Implementierung wird aufgerufen. Den echten Aufruf-Pfad von der API zum Task vor Refactoring dokumentieren.
2026-03-06 | Render-Pipeline | render_order_line_task auf falschem Worker (kein Blender)
Problem: render_order_line_task war auf Queue step_processing → lief im worker-Container (Backend-Dockerfile, kein Blender). render_to_file() fiel still auf Pillow-Placeholder zurück. Renders scheinbar erfolgreich aber nur graue Platzhalterbilder.
Ursache: is_blender_available() prüft BLENDER_BIN-Env-Var — im worker-Container nicht gesetzt. Fallback auf Pillow passiert lautlos ohne Exception.
Lösung: render_order_line_task queue auf thumbnail_rendering geändert → läuft jetzt im render-worker-Container (hat Blender 5.0.1 + cadquery). worker-thumbnail-Service aus docker-compose.yml entfernt (hatte keinen Blender, blockierte aber die Queue).
Für künftige Projekte: Blender-Tasks IMMER auf thumbnail_rendering Queue routen. worker-thumbnail = kein Blender, render-worker = hat Blender. Wenn is_blender_available() False zurückgibt ist der Task auf dem falschen Worker.
2026-03-06 | Docker | worker-thumbnail vs render-worker — beide auf thumbnail_rendering
Problem: Sowohl worker-thumbnail (kein Blender) als auch render-worker (hat Blender) lauschten auf thumbnail_rendering Queue. Tasks wurden round-robin verteilt → 50% der Blender-Tasks schlugen fehl (Pillow-Fallback, kein echter Fehler).
Lösung: worker-thumbnail-Service aus docker-compose entfernt. render-worker ist der alleinige Consumer von thumbnail_rendering. Dieser hat Blender + cadquery + alle Render-Scripts.
Für künftige Projekte: Nie zwei Services mit unterschiedlichen Capabilities auf die gleiche Queue hören lassen.
2026-03-06 | Multi-Tenancy | tenant_id NOT NULL verletzt bei Order-Erstellung
Problem: Migration 036 machte tenant_id NOT NULL auf orders, order_lines, order_items. Alle Create-Endpoints übergaben tenant_id nicht → PostgreSQL NOT NULL Constraint Violation.
Lösung: Überall tenant_id=getattr(user, 'tenant_id', None) in Model-Konstruktoren: orders.py (create_order, split_order, add_line_to_order), uploads.py (finalize_excel).
Für künftige Projekte: Nach jeder RLS-Migration alle Create-Endpoints prüfen ob das neue Pflichtfeld befüllt wird. getattr(user, 'tenant_id', None) als sicheres Default-Pattern verwenden.
2026-03-06 | Celery | render_order_line_task auf falscher Queue → Pillow-Fallback
Problem: render_order_line_task war auf step_processing Queue → wurde von worker-Container bearbeitet, der kein Blender hat. is_blender_available() → False → Pillow-Placeholder-Bild ohne Fehlermeldung.
Lösung: Queue zu thumbnail_rendering geändert → nur render-worker (mit Blender 5.0.1) verarbeitet diese Tasks.
Für künftige Projekte: Nach jeder Architektur-Änderung (Container-Entfernung, Queue-Umbenennung) alle Celery-Task-Dekoratoren prüfen ob sie noch auf dem richtigen Worker laufen.
2026-03-06 | Celery | Zwei Worker auf derselben Queue mit unterschiedlichen Fähigkeiten
Problem: worker-thumbnail und render-worker konkurrierten auf thumbnail_rendering. worker-thumbnail hatte kein Blender → 50% aller Render-Tasks liefen auf dem falschen Worker → Silent-Fail.
Lösung: worker-thumbnail aus docker-compose.yml entfernt. render-worker ist einziger Consumer von thumbnail_rendering.
Regel: Jede Queue sollte nur von Workers mit identischen Fähigkeiten konsumiert werden. Nie zwei Worker unterschiedlicher Ausstattung auf dieselbe Queue setzen.
2026-03-06 | Python | Circular Import via doppelte Shim-Schicht
Problem: template_service.py importierte aus domains/rendering/service.py, das wiederum aus template_service.py importierte. Beide waren leere Shims. resolve_template() war nie aufrufbar → Render-Tasks crashing mit ImportError.
Lösung: Volle Implementierung in template_service.py wiederhergestellt (aus git history). domains/rendering/service.py importiert nur davon — kein Rückimport.
Für künftige Projekte: Shim-Layer immer auf circular imports prüfen. domains/X/service.py sollte entweder die echte Implementierung enthalten ODER aus einer anderen Domain importieren, aber nicht im Kreis.
2026-03-06 | FastAPI | 307-Redirect verliert Authorization-Header
Problem: GET /api/tenants → 307 Temporary Redirect zu /api/tenants/ (trailing slash). axios folgt dem Redirect, verliert dabei den Authorization-Header → 401 → leere Tenant-Liste im Frontend.
Lösung: Frontend-API-Call auf /tenants/ mit trailing slash geändert.
Für künftige Projekte: FastAPI-Router immer mit trailing slash aufrufen oder redirect_slashes=False am Router setzen.
2026-03-06 | Render-Pipeline | ffmpeg Turntable hängt ohne shortest=1
Problem: Turntable-Render (Order f0436188) mit bg_color schlug mit Timeout (300s) fehl. ffmpeg-Overlay-Befehl war [1:v][0:v]overlay=0:0 — der lavfi color-Quell-Stream hat unendliche Dauer. ffmpeg wartete nach Ende der PNG-Sequenz weiter auf weitere Farb-Stream-Frames → hing unbegrenzt.
Lösung: overlay=0:0 → overlay=0:0:shortest=1. shortest=1 beendet den Output-Stream sobald der kürzeste Input-Stream endet (die PNG-Sequenz).
Datei: backend/app/services/render_blender.py:507
Für künftige Projekte: Bei ffmpeg-Overlays mit lavfi/color/nullsrc als ein Input IMMER shortest=1 setzen. Sonst hängt ffmpeg nach Ende des finite Streams.
2026-03-06 | Architektur | WebSocket Auth via Query-Parameter (JWT)
Problem: WebSocket-Verbindungen können keinen Authorization-Header senden (Browser-WebSocket-API hat keine Header-Unterstützung). JWT muss anders übertragen werden.
Lösung: JWT als Query-Parameter: ws://host/api/ws?token=<jwt>. Backend verifiziert via jwt.decode() im WebSocket-Endpoint.
Sicherheitshinweis: Token ist in Server-Logs sichtbar. Für v2 akzeptabel. In v3: kurzlebigen WS-Token (TTL 30s) aus JWT generieren.
Für künftige Projekte: Immer Query-Param oder Cookie (bei HTTPS) für WebSocket-Auth verwenden; nie erwarten dass der Browser Headers setzen kann.
2026-03-06 | Celery Inspect | active_queues() zum Worker-Capability-Check
Erkenntnis: celery_app.control.inspect().active_queues() gibt pro Worker zurück welche Queues er konsumiert. Damit kann man gezielt prüfen ob ein Worker mit bestimmten Fähigkeiten (z.B. thumbnail_rendering) connected ist — besser als Worker-Namen-Heuristiken.
Anwendung: GET /api/worker/health/render nutzt active_queues() um render_worker_connected und blender_available korrekt zu bestimmen.