Zurück zum CV

Portfolio

Eine Auswahl der Projekte, an denen ich mit Claude-Code gearbeitet habe. Jede Karte zeigt die eingesetzte Technik, die wichtigsten Lernpunkte und ein Highlight. Über den Link unten landest du fokussiert in der Themen-Galaxie.

52 Projekte

Screenshot von rag-stack

rag-stack

Self-hosted Agent-Governance- und RAG-Plattform für meine KI-Infrastruktur

06.2026 – 07.2026120 Sessionsweb
bashdockerflaskhtmljavascriptlangchainnumpypytestpythonsqlite

rag-stack ist das Rückgrat meiner selbst gehosteten KI-Infrastruktur: eine Plattform, die KI-Agenten verwaltet und das gemeinsame Wissen (Retrieval-Augmented Generation) für alle meine Projekte bereitstellt. Ich habe sie gebaut, weil mehrere lokale und Cloud-Agenten denselben Wissensstand, dieselben Regeln und nachvollziehbare Abläufe brauchen, statt isoliert zu arbeiten. Statt eines monolithischen Systems läuft rag-stack als eigenständiger Dienst, der über HTTP eine Orchestrierungs-Oberfläche, Lesson-Speicher, Embedding-Pipeline und Governance-Regeln anbietet.

Technisch ist es ein Flask-Service hinter Gunicorn, betrieben in einem eigenen Container mit eigener SQLite-Datenbank, sauber per HTTP von der Web-Framework-Schicht getrennt. Die Wissensbasis nutzt lokale Embeddings über Ollama (nomic-embed-text mit 768 Dimensionen, bge-m3 mit 1024) und kombiniert FTS5-Volltextsuche mit semantischer Suche, Cross-Encoder-Reranking und Multi-Query-Rewriting. Jede gespeicherte Lesson trägt Herkunft, Vertrauensstufe, Autor und Status, KI-generierte Einträge bleiben bis zur menschlichen Freigabe aus der Suche gefiltert. Ein Secret-Scan vor jedem Schreibvorgang und ein durchgängiges Fail-Closed-Prinzip sichern die Datenhygiene ab.

Der Umfang ist über die Zeit beachtlich gewachsen: rund 120.000 Zeilen Code über etwa 177 Arbeitssitzungen seit Mai 2026. Dazu gehören 13 autonome Hintergrund-Worker, die per systemd-Timer regelmäßig Audits, Drift-Erkennung, Sicherheits- und Backup-Checks fahren, 24 host-weite CLIs für Kontext, Memory-Sync und Agent-Dispatch, sowie eine additive Plattform-Schicht mit Windmill-ähnlichen Primitiven wie verschlüsseltem Secret-Store, typisierten Ressourcen, RBAC und Suspend/Resume. Das System ist live und im Dauerbetrieb hinter Reverse-Proxy und SSO.

Lernpunkte

  • Strikte HTTP-only-Trennung zweier Dienste mit eigener DB, eigenem Port und eigenem Dockerfile macht jeden Teil unabhängig deploy- und ausfallbar, ohne den anderen mitzureißen.
  • Lokale Embeddings über Ollama liefern eine kostenlose, offline-fähige Wissensbasis und vermeiden laufende API-Kosten bei sich änderndem Korpus.
  • Hybride Suche aus FTS5-Volltext plus semantischem Vektor-Retrieval mit anschließendem Reranking liefert deutlich robustere Treffer als ein einzelnes Verfahren allein.
  • Ein Provenienz- und Vertrauensmodell pro Wissens-Eintrag, bei dem KI-generierte Inhalte bis zur menschlichen Prüfung gefiltert bleiben, verhindert, dass sich unverifizierte Aussagen im Korpus festsetzen.
  • Sicherheitskritische Pfade konsequent auf Fail-Closed auszulegen und Secret-Scans vor jedem Schreibvorgang zu erzwingen, verhindert Leaks systematisch statt reaktiv.
Screenshot von glug

glug

Event-getriebenes Flask-Web-Framework mit Plugin-System und Update-Kanälen

07.20266 Sessionsweb
bashdockerflaskhtmljavascriptmarkdownpydanticpythonsqlalchemywebauthn

Glug ist ein selbst entwickeltes Web-Framework auf Basis von Python und Flask, aus dem konkrete Apps abgeleitet werden – etwa eine Studio-Buchungsseite und weitere Kundenprojekte. Ich habe es gebaut, weil ich für mehrere kleine Web-Auftritte eine gemeinsame Basis brauchte, die ich zentral pflegen und gleichzeitig pro Mandant erweitern kann, ohne jedes Mal von vorne anzufangen. Statt jede Seite einzeln zu warten, liefert ein Server-Repo das Framework, und die einzelnen Apps ziehen Updates über definierte Kanäle nach.

Architektonisch ruht Glug auf drei Mechanismen. Erstens eine Event-Driven-Architecture über Blinker-Signals als einzige Cross-Modul-Kommunikation, sodass kein Modul ein anderes direkt importiert (Buchung, Rechnung, Newsletter reagieren entkoppelt auf Events). Zweitens ein manifest-getriebenes Plugin-System mit eigenen, versionierten und idempotenten DB-Migrationen, Signal-Subscriptions, Blueprints und Auto-Einbindung in die Navigation – inklusive Hot-Reload der Handler ohne Neustart. Drittens ein Page-Framework mit zentraler register_page-Registry, das Routing, Layout-Dispatch sowie Sitemap, JSON-LD und llms.txt automatisch aus denselben Events generiert. Dazu kommen ein kanalbasiertes Update-System (current/beta/dev) mit Heartbeat, Smoke-Gate und Rollback-on-Fail sowie eine SQLite-Schicht im WAL-Modus, die Nutzerdaten über eine Whitelist strikt von schema-migrierten Tabellen trennt.

Der Umfang ist beachtlich: rund 253k Lines of Code über etwa 93 Arbeitssessions zwischen Mai und Juni 2026, mit gepinnten Dependencies und CI-Gates (bandit, pip-audit, Konventions-Checks) sowie einem schlanken eigenen Smoke-Test-Framework mit knapp 300 Tests in unter fünf Sekunden. Sicherheit ist durchgängig verdrahtet: deny-by-default CSRF, Rate-Limits für Login und Buchung, nonce-basierte CSP ohne unsafe-eval, magic-link Portal-Tokens und gehärteter Datei-Upload. Das Framework läuft produktiv und beliefert mehrere abgeleitete Apps.

Lernpunkte

  • Eine Event-Driven-Architecture über Signals entkoppelt Module sauber, verlagert die Komplexität aber in das Verständnis der Laufzeit-Verdrahtung statt in explizite Imports.
  • Plugin-DB-Migrationen müssen idempotent und versioniert sein, und der Single-Statement-Runner verzeiht keine Mehrfach-Statements oder SQL-Dateipfade – das war eine schmerzhafte, fleet-weite Boot-Falle.
  • Eine strikte CSP ohne unsafe-inline killt inline onclick/onsubmit-Handler stillschweigend, weshalb destruktive Aktionen ohne Bestätigung feuern können – delegierte data-Attribute oder addEventListener in nonce-Skripten sind die Lösung.
  • Authorisierung muss zwischen Page und mutierender Sub-Route konsistent sein; login_required prüft keine Rolle, admin_required schon – eine Lücke, die ein Nicht-Admin sonst ausnutzen kann.
  • Ein kanalbasiertes Update-System mit Smoke-Gate und automatischem Rollback macht das Ausrollen über mehrere Mandanten wartbar, verlangt aber harte Trennung von Nutzerdaten und schema-migrierten Tabellen.
Screenshot von bugtest-stack

bugtest-stack

Geplanter Bug-Aggregator und Smoke-Test-Runner für meine Self-Hosted-Projekte

0 Sessionsdevops
flaskjinjapytestpython

bugtest-stack ist mein Entwurf für ein hostweites Qualitäts-Backbone über alle Projekte meiner Self-Hosted-Umgebung. Die Idee sind zwei schlanke Flask-Dienste: ein Bug-Aggregator, der Issues aus den vorhandenen Git-Repositories zusammenführt, ohne eine zweite Quelle der Wahrheit zu erzeugen, sowie ein Smoke-Test-Runner, der pro Projekt eine versionierte Testdefinition ausführt und bei Fehlern automatisch ein Issue anlegt. Gebaut habe ich es, weil mit wachsender Projektzahl der Überblick verloren geht, welche Dienste laufen und wo Regressionen liegen.

Architektonisch trennt der Entwurf zwei zustandsarme Dienste hinter einem Reverse-Proxy mit Forward-Auth für die Oberfläche und Bearer-Token für die API, sodass auch automatisierte Agenten den Stack bedienen können. Geteilte Logik liegt in einer gemeinsamen Python-Bibliothek (Git-Client, Projekt-Discovery, Auth, Memory-Anbindung). Tests sind als smoke.yaml im jeweiligen Repo abgelegt, branch-bewusst und maschinenlesbar, mit Runner-Typen für HTTP und Shell in Phase 1 sowie Browser- und pytest-Läufen später. Persistenz bewusst minimal: SQLite plus Dateisystem für Artefakte, Redis nur als Queue. Der Fehlerpfad ist als Schleife gedacht, inklusive Deduplizierung über einen normalisierten Fehler-Hash, damit dieselbe Regression kein zweites Issue erzeugt.

Der aktuelle Stand ist ehrlich gesagt Spezifikation plus Gerüst, kein laufendes System. Vorhanden sind eine ausgearbeitete Design-Spec, ein 18-Task-Implementierungsplan nach Test-Driven-Development, das Python-Paketskelett, ein Issue-Template und ein smoke.yaml, das vorerst nur prüft, ob die Pakete importierbar sind. Die eigentlichen Service-Module stehen noch aus. Spannend war für mich weniger der wenige Code als die Methode: Ich habe die Aufgaben als Git-Issues formuliert und über eine Webhook-Pipeline an autonome Coding-Agenten verteilt, statt selbst zu implementieren.

Lernpunkte

  • Ein Aggregator, der Git als einzige Quelle der Wahrheit für Issues behält, ist robuster als ein zweiter Tracker mit eigener Datenhaltung.
  • Testdefinitionen als versioniertes smoke.yaml direkt im Projekt-Repo zu halten macht sie branch-bewusst, maschinenlesbar und ohne separate Pflege aktuell.
  • Deduplizierung über einen normalisierten Fehler-Hash (Projekt plus Test-ID plus bereinigte Fehlersignatur) verhindert Issue-Fluten bei wiederkehrenden Regressionen.
  • Forward-Auth für die UI und Bearer-Token für die API in einem Dienst zu kombinieren erlaubt menschliche und agentische Bedienung über denselben Endpunkt.
  • Spec-driven plus ein TDD-Plan als Git-Issues lässt sich an autonome Coding-Agenten verteilen, deckt aber auch auf, dass Agenten nur die explizit im Auftrag genannte Akzeptanz umsetzen, nicht stillschweigend die Plandatei.
Screenshot von cv-website

cv-website

Lebenslauf-Site mit KI-gestütztem Bewerbungs-Workflow, 8 Backends und 20 Branchen-Themes

0 Sessionsweb
bashcsshtmljavascriptpython

cv.joelduss.xyz ist meine Lebenslauf-Site, kombiniert mit einem KI-gestützten Bewerbungs-Workflow für die Stellensuche in der Schweiz. Das öffentliche Frontend ist bewusst statisch gehalten — Vanilla HTML, CSS und JavaScript ohne Build-Pipeline, ohne CDNs, ohne Tracker, gespeist aus einer einzigen cv.json als Datenquelle. Der eigentliche Kern liegt im geschützten Admin-Backend: Aus einer Job-URL wird eine vierstufige Pipeline (parse, score, cover, revise), die ein Inserat scrapt, gegen meinen Lebenslauf bewertet, ein Anschreiben entwirft und es nach meiner Anweisung inkrementell überarbeitet. Gebaut habe ich es, weil generische ChatGPT-Wrapper weder die JSON-Strenge noch die Wahrheits-Constraints liefern, die ich für reale Bewerbungen brauche.

Architektonisch ist jede Pipeline-Stufe ein eigener JSON-Vertrag und kann ein eigenes von acht Backends fahren: Claude-, Gemini- und Codex-CLI, Anthropic-, OpenAI- und OpenRouter-API sowie zwei lokale Inferenz-Pfade über Ollama und OpenVINO auf einer Intel Arc Pro B50. Die Modell-Dropdowns füllen sich live aus dem /v1/models-Endpoint des jeweiligen Providers. Das Backend ist Python 3.13 mit Flask 3 und Gunicorn in einem non-root-Container (uid 10001, nur an 127.0.0.1 gebunden, Caddy davor mit Authelia-SSO). Persistenz läuft über SQLite im WAL-Modus mit einem eigenen Migrations-System, AI-Calls und Scraping über einen Threadpool-Worker, Provider-API-Keys liegen Fernet-verschlüsselt at-rest, Passwörter als Argon2id-Hash. Ein 20-Theme-System injiziert pro Bewerbungs-Branche ein eigenes Layout zur Laufzeit über /themes/active.css, ohne Rebuild und mit separatem Print-Stylesheet je Theme.

Der Umfang ist über vier Arbeitssitzungen gewachsen (rund 42'000 Zeilen, 14 Backend-Module, 18 Test-Dateien). Schweiz-spezifische Funktionen runden es ab: automatisches ÖV- und Auto-Routing zum Job-Standort via SBB-Transport-API und OSRM als Pendel-Kontext fürs Anschreiben, sowie ein automatisch generiertes RAV-Formular 716.007 als PDF, das mir das monatliche Abtippen der Bewerbungen erspart. Zusätzlich habe ich einen autonomen Job-Mail-Agenten gebaut — eine persistente Claude-Agent-SDK-Schleife mit In-Process-MCP-Tools für Gmail, dreischichtiger Sandbox und fail-closed Auto-Send-Gate; der Code ist vollständig, getestet und security-reviewed, der Live-Betrieb wartet nur noch auf die einmalige OAuth-Freigabe.

Lernpunkte

  • Eine Pipeline mit pro-Stufe austauschbaren Backends braucht strikte JSON-Verträge je Stufe (mit Regex-Fallback gegen gesprächige Modelle), sonst bricht der Vertrag zwischen den Stufen.
  • Lokale Inferenz auf der Intel Arc Pro B50 (Mistral-Small-24B-int4 via OpenVINO) liefert für Schweizer Anschreiben natürlicheres Deutsch als Cloud-Modelle — zu null Kosten und ohne Datenabfluss.
  • Laufzeit-Theming über eine injizierte active.css mit separatem Print-Stylesheet trennt Inhalt sauber von Darstellung und erlaubt Branchen-Looks ohne Rebuild.
  • Bei autonomem Mail-Versand ist das gefährlichste Fenster ein Doppel-Reply nach erfolgreichem Senden aber fehlgeschlagenem Status-Update — gelöst über Idempotenz pro Thread und ein fail-closed Auto-Send-Gate.
  • Secrets gehören Fernet-verschlüsselt in die DB statt in Logs oder HTML, und ein non-root-Container mit 127.0.0.1-Bind hinter Caddy/Authelia hält die Angriffsfläche klein.
Screenshot von echo-dashboard

echo-dashboard

Always-On-Wanddisplay auf einem Echo Show 5 mit LineageOS, gespeist aus 16 Datenquellen

0 Sessionsweb
bashcssdockerhtmljavascriptpython

Ich habe einen Amazon Echo Show 5 zu einem freien Android-Gerät umgeflasht (LineageOS 18.1) und ihn in ein Always-On-Wanddisplay verwandelt. Ausgangspunkt war ein eigener Bedarf: Ich wollte den Verbrauch und Status meiner AI-Werkzeuge auf einen Blick sehen, ohne dafür eine Cloud-Oberfläche zu öffnen. Das Dashboard aggregiert dafür vier Quellen - die Anthropic Admin Usage API, OpenRouter-Guthaben sowie lokal mitgeschriebene Token-Logs von Codex- und Gemini-CLI - und zeigt sie auf dem 960x480-Display.

Technisch besteht das System aus drei Teilen. Ein FastAPI-Backend mit SQLite läuft in Docker, bewusst LAN-only abgesichert (iptables-Regel statt Reverse-Proxy, keine Public-Exposure, Read-only-View ohne Mutationen). Es sammelt Daten über asynchrone Poller und einen Scheduler, cached die letzten gültigen Werte und markiert veraltete Stände mit einem Stale-Indikator, falls eine Quelle ausfällt. Das Frontend ist bewusst dependency-frei: Vanilla HTML, CSS und JavaScript im Scandi-Stil, das über rotierende Views läuft und per Gerät unterschiedlich konfiguriert werden kann. Den dritten Teil bildet eine kleine Kotlin-WebView-App, die ich als Default-Launcher installiere, sodass das Gerät nach Boot oder Home-Tap direkt im Dashboard landet.

Im Lauf des Projekts ist aus dem AI-Quoten-Monitor ein Familien-Dashboard geworden: Inzwischen liefern 16 Poller Daten zu Sonos und Spotify, Home Assistant, Wetter und 7-Tage-Vorhersage, Nachrichten, ÖV-Abfahrten, Aare-Wassertemperatur und den lokalen Abfuhrterminen, dazu Views wie Bilderrahmen-Galerie, Küchentimer, geteilte Einkaufsliste, Familiennotiz, Geburtstage und ein Nacht-Dimm-Modus. Der Stand: rund 10.600 Zeilen produktiver Code, 34 Test-Dateien, 102 Commits über etwa drei Wochen, in Betrieb auf dem Wohnzimmer-Gerät.

Lernpunkte

  • Ein verworfenes Konsumgerät lässt sich per Custom-ROM und ADB-Default-Launcher zu einem brauchbaren Always-On-Panel umnutzen, statt es zu ersetzen.
  • Eine LAN-only-Absicherung per iptables und Read-only-Design ist für ein Heimgerät pragmatischer und risikoärmer als ein voller Reverse-Proxy mit Auth.
  • Ein Last-Good-Cache mit sichtbarem Stale-Indikator hält ein Dashboard verlässlich, auch wenn einzelne externe Quellen zeitweise nicht antworten.
  • Lokale CLI-Logs (Codex, Gemini) lassen sich als Datenquelle erschliessen, wenn keine offizielle API existiert - eigene Parser schlagen die Brücke.
  • Ein dependency-freies Vanilla-Frontend bleibt auf schwacher Hardware mit kleinem Display schnell und wartbar, ohne Build-Pipeline.
Screenshot von eveline

eveline

Wolle- und Häkel-Onlineshop als Mandant einer eigenen Multi-Tenant-Web-Plattform

0 Sessionsai-llm
htmlmarkdown

eveline ist ein produktiver Onlineshop für handgehäkelte Amigurumi und Wollwaren, der nicht als Einzelanwendung, sondern als eigener Mandant auf einer von mir gebauten Multi-Tenant-Web-Plattform läuft. Statt für jeden Shop eine separate Codebasis zu pflegen, teilen sich mehrere Sites (darunter ein Yoga-Studio und eine Astrologie-Seite) denselben Framework-Code; jeder Mandant bekommt eine eigene Datenbank und eigene Theme-Überschreibungen. eveline war für mich der Praxistest, ob diese Mehrmandantenfähigkeit wirklich trägt: dasselbe Shop-, Portal- und Inhalts-System, aber mit komplett anderem Sortiment, eigenem Branding und eigenen Inhalten.

Technisch besteht die Plattform aus einem Python/Flask-Backend mit Plugin- und Modul-Architektur (Shop, Kundenportal, Blog), serverseitig gerenderten Jinja-Templates und SQLite pro Container. Das Design folgt einem eigenen, skandinavisch geprägten Designsystem (Serif-/Sans-Kombination, eine einzige Akzentfarbe, warme Papierfläche, mobile-first ab 375 px). Der Shop deckt den vollen Kauf-Pfad ab: Produktseiten mit Farbvarianten und Galerie, serverseitig gehärteter Warenkorb (Mengen-Clamp, CSRF, atomare Bestandsführung über `BEGIN IMMEDIATE`), Checkout, Bestellungen und ein magic-link-/PIN-/Passkey-basiertes Kundenportal. Deployment läuft über ein schlankes Docker-Image hinter Reverse-Proxy mit TLS, HSTS und CSP. Die Entwicklung selbst war agentengetrieben: Ich habe als Architekt Issues, Architektur-Entscheide und Reviews geführt, die eigentliche Code-Arbeit über mehrere LLM-Worker verteilt und sicherheitskritische oder schwer umkehrbare Änderungen per N-Wege-Cross-Verify gegengeprüft.

eveline ist live und bestückt (rund ein Dutzend Produkte mit Farbvarianten, vollständiger Kauf-Flow). Das eigentliche Repository ist bewusst eine schlanke Governance-Hülle: Es enthält Issues, Architecture-Decision-Records, Agent-State, Design-Specs und Audit-Artefakte, während der gemeinsame Anwendungscode im Framework-Repo liegt. Der Shop hat einen externen Sicherheits-/Bugcheck über die öffentliche IP durchlaufen (TLS, HSTS, CSP, Cookies, CSRF, Upload-Auth und Bestands-/Preislogik unauffällig; offene Punkte betrafen Tenant-Isolation bei SEO-Metadaten). Größere Features wie bestandsgenaue Varianten oder Back-in-Stock-Benachrichtigung sind als priorisierte, risikobewertete Specs dokumentiert statt blind umgesetzt.

Lernpunkte

  • Echte Mehrmandantenfähigkeit zwingt zur strikten Trennung von geteiltem Code und mandantenspezifischem Zustand: Verhalten wird pro Tenant gegated, nie wird Framework-Code für einen einzelnen Shop geforkt.
  • Seed-/Migrations-Skripte für produktive Mandanten müssen idempotent und nicht-überschreibend sein (set-if-new, page-save-only-if-new), sonst kippt ein erneuter Lauf vom Admin gepflegte Live-Einstellungen.
  • Ich habe gelernt, Build- und Deploy-Pfade explizit zu verifizieren statt der Doku zu vertrauen: Ein vermeintlicher Compose-Build war ein No-Op, das echte Image entstand aus einem anderen Repo, und nur ein Grep im laufenden Container bestätigt, dass eine Änderung wirklich live ist.
  • Eine vorgeschaltete externe Sicherheitsprüfung (TLS/HSTS/CSP/CSRF/Bestandslogik) trennt verlässlich echte Lücken von False Positives und zeigt, dass Tenant-Isolation auch SEO- und Metadaten umfassen muss, nicht nur Auth.
  • Hochriskante Änderungen wie ein neuer Warenkorb-Schlüssel werden zuerst als Design-Spec mit offenen Entscheidungen und Touchpoint-Plan geschrieben und per Cross-Verify gegengeprüft, statt sie direkt zu implementieren.
Screenshot von pokemon

pokemon

Browser-MMO auf Basis von Pokémon Emerald: Python-Engine im Browser via Pyodide

0 Sessionsgames
bashdockerhtmlplaywrightpytestpython

Ich habe einen Online-Multiplayer-Aufsatz auf den offenen pokeemerald-Decomp gebaut: ein lokal-first Web-MMO, das ohne Download, ohne Emulator und ohne App direkt im Browser läuft. Spielmechanik, Maps und Skripte stammen aus dem Originaldekompilat, die Engine, der Netzwerk-Layer und das Frontend sind dagegen vollständig in Python neu geschrieben. Reizvoll war für mich die Kombination aus drei Dingen, die selten zusammenkommen: ein in WebAssembly laufender Python-Spielcode, eine Mehrspieler-Präsenz auf gemeinsamen Routen und Towns, und Trainer-NPCs, die über ein lokal gehostetes LLM sprechen statt über eine Cloud-API.

Architektonisch ist es ein Monorepo mit klarer Rollentrennung: Der Spielcode läuft per Pyodide als WASM im Browser, geladen aus einer von einem Service-Worker gecachten Web-Shell, was Offline-Betrieb erlaubt. Ein Flask-Backend mit SQLite hält Save-Slots, Authentifizierung und die Spieler-Präsenz. Ein Importer-Modul liest die pokeemerald-Map-Dateien direkt über einen Adapter ein und stitcht mehrere Decomp-Quellen (Emerald, FireRed, Crystal) zu einer Welt zusammen; NPC-Positionen werden deterministisch pro Tick aus einem Hash berechnet, damit alle Clients ohne Server-Roundtrip dieselbe Bewegung sehen. Sprites entstehen über eine ComfyUI-LoRA-Pipeline, die NPC-Dialoge über Ollama. Die UI ist bewusst Gamepad- und D-Pad-first für Handhelds wie den Anbernic ausgelegt.

Entwickelt habe ich das Projekt über rund 21 Sessions in einem agentengetriebenen Workflow: Issues mit präzisen Akzeptanzkriterien werden an spezialisierte LLM-Subagents dispatcht, ich reviewe die Diffs, teste über vier Ebenen (Unit, Integration, Browser via Playwright, Vision via minicpm-v) und merge. Der Umfang liegt bei etwa 243k Zeilen Code über Server, Client, Webclient und Importer; in den intensivsten 24 Stunden wurden über 30 Pull Requests gemerged. Das Spiel ist live und spielbar, das Backlog ist in Sprints und EPICs organisiert.

Lernpunkte

  • Python-Spielcode lässt sich via Pyodide als WASM komplett im Browser ausführen, erfordert aber ein deterministisches Cache-Bust-Verfahren über alle drei Cache-Schichten (Service-Worker, HTTP-Cache, Pyodide-FS), sonst werden alte Bytes ausgeliefert trotz passender Disk-SHA.
  • Deterministische NPC-Bewegung pro Tick aus einem Hash spart einen Server-Roundtrip und hält alle Clients ohne zusätzliche Synchronisation konsistent.
  • Eine saubere State/Render/Input-Trennung ist die Voraussetzung dafür, dass sowohl LLM-NPCs als auch ein autonomer Test-Agent dieselbe Spiel-Oberfläche ansteuern können.
  • Beim parallelen Arbeiten mit isolierten Git-Worktrees teilt sich der Index, weshalb explizites Stagen einzelner Pfade statt git add -A nötig ist, um Leck-Commits und überschriebene Branches zu vermeiden.
  • Vier Test-Ebenen pro Merge (Unit, Integration, Browser, Vision) fangen unterschiedliche Fehlerklassen ab, die ein einzelner Layer übersieht.
Screenshot von powershell

powershell

PowerShell-7-Verwaltungsskripte mit Docker-Testsandbox und Auto-Katalog

0 Sessionstools
bashpowershell

Im Arbeitsalltag fallen wiederkehrende Windows-Verwaltungsaufgaben an, etwa das Anlegen von Dateifreigaben samt zugehöriger Active-Directory-Gruppen und ACLs. Statt solche Schritte jedes Mal manuell auszuführen, habe ich begonnen, sie als saubere, wiederverwendbare PowerShell-7-Skripte abzulegen. Kern ist ein Tool, das per GUI einen Ordner anlegt, die passenden Lese- und Schreibgruppen im AD erstellt und die Berechtigungen setzt. Mir war dabei wichtig, das nicht als loses Skript-Sammelsurium zu führen, sondern mit Konventionen, Tests und Auffindbarkeit.

Architektonisch trenne ich konsequent reine, AD- und GUI-freie Kernlogik (unter scripts/lib/) von den Teilen, die Active Directory, Netzlaufwerke oder Windows.Forms brauchen. Dadurch lässt sich die Logik in einer Docker-Sandbox auf Basis des offiziellen pwsh-LTS-Images testen, ganz ohne Windows-Host: PSScriptAnalyzer plus Pester laufen über das ganze Repo. AD- und GUI-Teile werden separat auf einem Windows-Host mit RSAT geprüft. Netzgebundene Aufrufe sind über einen eigenen Retry-Helfer mit exponentiellem Backoff gekapselt, der nur bei transienten Fehlertypen wiederholt und Validierungs- oder Berechtigungsfehler sofort durchreicht.

Damit Skripte auffindbar bleiben, generiert das Repo aus den Comment-Based-Help-Synopsen automatisch einen Katalog (INDEX.md) — über zwei formatgleiche Generatoren in PowerShell und Bash, dazu Tooling zum Anlegen neuer Skripte aus einer Vorlage mit erzwungener Verb-Noun-Konvention. Der Umfang ist bewusst kompakt: ein produktives GUI-Tool, einige Bibliotheks-Helfer, eine sandbox-getestete Beispielsammlung (UI, Logging, Parallelisierung) und die zugehörigen Pester-Tests. Es ist eine wachsende, gepflegte Werkzeugkiste, kein abgeschlossenes Produkt.

Lernpunkte

  • Reine Logik strikt von AD/GUI/Netz zu trennen macht PowerShell auf einem Linux-Host per Docker und Pester testbar, ohne Windows zu brauchen.
  • Ein Retry-Wrapper mit exponentiellem Backoff sollte nur transiente Fehlertypen wiederholen und Validierungs-/Berechtigungsfehler sofort weiterwerfen, sonst kaschiert man echte Bugs.
  • Ein aus den .SYNOPSIS-Blöcken automatisch generierter Index hält eine Skriptsammlung dauerhaft auffindbar, statt dass Skripte verloren gehen.
  • Zwei formatidentische Index-Generatoren (pwsh und bash) sichern dasselbe Ergebnis sowohl host-nativ als auch unter Windows ab.
  • Zustandsändernde Kernfunktionen mit SupportsShouldProcess (-WhatIf) auszustatten erlaubt risikoarmes Testen von AD- und ACL-Operationen.
Screenshot von rahoot

rahoot

Self-gehostete Kahoot-Alternative, gehärteter Fork für ein reales Firmen-Event

0 Sessionsweb
bashhtmljavascriptmarkdown

Ich habe eine Open-Source-Quizplattform im Kahoot-Stil (das Projekt Razzia, vormals Rahoot) geforkt und für ein internes Firmen-Personalfest produktionsreif gemacht. Statt ein fertiges Tool nur zu deployen, habe ich den Fork über 50 Commits hinweg substanziell weiterentwickelt: native Theming-Funktion, Stabilität für den Live-Betrieb mit dutzenden gleichzeitigen Spielern, Sicherheits-Hardening und einen optionalen Hardware-Aufsatz. Ziel war ein Spielabend, den ich allein vom Smartphone aus moderieren kann, mit dem Beamer als reinem Anzeigegerät.

Technisch ist es ein TypeScript-Monorepo (pnpm-Workspace) mit drei Paketen: ein React-19-Frontend (TanStack Router, Tailwind v4, Vite, PWA) für Spieler und Moderator, ein socket.io-Server auf Node für das Live-Spiel und ein gemeinsames Paket für Typen und Konstanten (zod-validiert). Ausgeliefert wird ein selbstgebautes Docker-Image hinter einem Caddy-Reverse-Proxy mit TLS. Meine größten Eigenbauten: eine native Theming-Schicht (pro Ansicht eigene Hintergründe und Farben, live im Moderator-Dashboard editierbar, via CSS-Variablen auf die ganze UI angewandt), eine Crash-Recovery (atomarer State-Snapshot ins Host-Volume, der einen Container-Neustart oder Absturz übersteht und Punkte plus Rangliste wiederherstellt), ein /healthz-Endpoint mit Docker-HEALTHCHECK und Graceful-Shutdown sowie Performance-Arbeit (Route-Code-Splitting, Vendor-Chunking, webp), die das initiale Payload von rund 3,8 MB auf rund 1,1 MB gesenkt hat.

Das System lief beim echten Event und wurde vorher mit skriptgesteuerten socket.io-Clients bis 600 gleichzeitige Verbindungen ohne Fehler lastgetestet (rund 7,5-faches Headroom für ein 80-Personen-Fest). Beim Security-Pass habe ich eine HIGH-Path-Traversal-Lücke geschlossen und eine ws-DoS-CVE gepatcht; die Testabdeckung habe ich von 21 auf über 115 Vitest-Tests gehoben und alle drei Pakete erstmals typecheck-sauber gemacht. Als optionalen, isolierten Aufsatz gibt es einen Raspberry-Pi-Kiosk ("Satellite"), der ohne Laptop in den Vollbild-Hostscreen bootet, während das Spiel komplett vom Telefon gesteuert wird.

Lernpunkte

  • Ein grüner Docker-Build beweist nichts: das vite/esbuild-Toolchain typecheckt und startet den Code nie, also kann ein erfolgreicher Build ein crash-loopendes socket-Bundle oder Cross-Package-Context-Bugs ausliefern, die erst ein Live-Smoke-Test findet.
  • Crash-Recovery über einen atomaren State-Snapshot ins Host-Volume macht Redeploys und Abstürze während eines Live-Events unkritisch, ohne eine Datenbank oder Redis einzuführen.
  • Per-IP-Rate-Limiting ist am physischen Veranstaltungsort kontraproduktiv, weil alle Spieler hinter einer NAT-IP sitzen; zod-Inputvalidierung und ein Security-Audit sind hier die richtige Schutzschicht.
  • Realistische Lasttests gehen mit skriptgesteuerten socket.io-Clients statt mit LLM-Agenten als Last-Generatoren; so liessen sich 600 parallele Verbindungen mit 0 Fehlern und p95 unter 530 ms belegen.
  • Eine strukturierte Gap-Analyse gegen ein Referenzprodukt half, billige High-ROI-Wins von Over-Engineering zu trennen und Features bewusst zu verwerfen, die für ein Self-Hosted-Event keinen Wert haben.
Screenshot von yoga

yoga

Event-getriebene Plugin-Plattform für Praxis-Software, aus einem Flask-Monolithen herausgelöst

0 Sessionsweb
dockerflaskhtmljavascriptmarkdownpydanticpytestpythonsqlalchemywebauthn

Ich habe eine modulare Web-Plattform für Yoga-Studios und verwandte Praxen (Therapie, Coaching, Astrologie) gebaut. Begonnen hat das Projekt als monolithische Flask-App für ein einzelnes Studio mit Buchung, Kundenverwaltung und Rechnungsstellung. Im Lauf der Entwicklung habe ich es zu einer event-getriebenen Plugin-Plattform umgebaut, auf der sich Funktionen wie Online-Meetings, Newsletter, Zahlungsabwicklung oder ein Point-of-Sale zur Laufzeit zu- und abschalten lassen, ohne den Container neu zu starten. Treiber war der Wunsch, dieselbe Codebasis für unterschiedliche Branchen wiederverwendbar zu machen statt sie pro Kunde zu forken.

Architektonisch trennen sich die Module strikt über einen Event-Layer: Cross-Modul-Kommunikation läuft ausschliesslich über Blinker-Signale, kein Modul importiert ein anderes direkt. Jedes Plugin liefert ein deklaratives Manifest mit eigenen Routen, Signal-Subscriptions, Settings-Schema und automatisch angewandten DB-Migrationen. Den ursprünglichen Datenbank-Monolithen (rund 2300 Zeilen) habe ich in über 40 Domain-Module aufgeteilt und dabei den Kern um etwa 58 Prozent verkleinert. Eine domain-agnostische appointments-Tabelle erlaubt es, neue Geschäftsfelder additiv anzubinden, ohne das Schema umzubauen. Der Stack ist bewusst schlank gehalten: Flask 3, SQLite im WAL-Modus, HTMX für server-gerendertes UI, Pillow für Bildverarbeitung, die Swiss-qrbill-Bibliothek für QR-Rechnungen sowie Talisman und Flask-Limiter für CSP und Rate-Limiting.

Die Plattform läuft produktiv hinter einem Caddy-Reverse-Proxy mit Authelia-SSO, deployt über Docker Compose. Inzwischen umfasst sie über 40 Plugins, rund 45 Datenbank-Domänen und mehr als 60 definierte Signale, abgesichert durch 235+ Smoke-Tests. Entwickelt habe ich grosse Teile mit mehreren parallelen KI-Coding-Sessions in einem Orchestrator-Pattern, in dem ich Aufträge als Issues spezifiziert, an Worker delegiert und die Diffs selbst reviewt und gemergt habe.

Lernpunkte

  • Ein Event-Layer mit Signalen entkoppelt Module so weit, dass neue Funktionen als Plugin angedockt werden, ohne bestehenden Code anzufassen.
  • Deklarative Plugin-Manifeste mit auto-angewandten DB-Migrationen ermöglichen Hot-Reload zur Laufzeit statt Deploy-Zyklen pro Feature.
  • Eine domain-agnostische Datentabelle mit Typ-Feld und JSON-Metadaten lässt neue Geschäftsfelder additiv anbinden, ohne Schema-Refactorings zu riskieren.
  • Das Aufteilen eines 2300-Zeilen-Datenbank-Monolithen in fokussierte Domain-Module senkt die kognitive Last und macht parallele Arbeit erst praktikabel.
  • Ein striktes Issue-first- und Diff-Review-Vorgehen ist die Voraussetzung dafür, viele parallele KI-Coding-Sessions ohne Qualitätsverlust zu koordinieren.