← Zurück zum Blog
pullmdclaude-codepwadocker29. März 2026 · 7 Min. Lesezeit

PullMD Teil 2: Vom Spec zum laufenden Container

SPEC → TESTS FIRST → CONTAINER · RUNNINGKI-generiertes Bild
Editorial-Diagramm: ein leuchtendes SPEC-Portal links, darüber ein 'TESTS FIRST'-Banner über vier gestapelten Test-Cards (NORMALIZE, FETCH, FALLBACK, INTEGRATION), dann ein CONTAINER-Panel rechts mit grünem RUNNING-Indikator; ein kleines Status-Panel unten links zeigt TDD · REVIEW · GREEN.Editorial-Diagramm: ein leuchtendes SPEC-Portal links, darüber ein 'TESTS FIRST'-Banner über vier gestapelten Test-Cards (NORMALIZE, FETCH, FALLBACK, INTEGRATION), dann ein CONTAINER-Panel rechts mit grünem RUNNING-Indikator; ein kleines Status-Panel unten links zeigt TDD · REVIEW · GREEN.

Die Spezifikation aus Teil 1 hatten wir am Abend des 2. März erstellt. Architektur durchdacht, API-Design geklärt, Kommentarformat entschieden.

Ich habe sie an Claude Code weitergegeben und damit gerechnet, dass es erst einmal einen Express-Server aufsetzt, die Reddit-Logik implementiert und irgendwann später vielleicht noch ein paar Tests dazuschreibt.

Aber das Erste, was Claude erstellte, waren Tests. Kein Server, keine Reddit-Logik, gar nichts davon.

Dass Test-driven Development (TDD) in den Superpowers-Skills fest verankert war, wusste ich zwar. Es dann aber tatsächlich zu sehen, wie Claude konsequent mit den Tests anfing, war dennoch interessant.

Vier Tests

Sie deckten genau das ab, was später die Architektur des Tools ausmachen sollte:

  • URL-Normalisierung als eigenes Modul
  • Reddit-JSON-Abruf über Mocks
  • HTML-Fallback, falls der JSON-Weg blockiert wird
  • ein separater Integrationstest für echte Reddit-Aufrufe

Die Tests beschrieben also nicht einfach nur ein “Tool, das Reddit abruft”.

Sie beschrieben ein Tool, das URLs zuerst zuverlässig normalisiert, Reddit über mehrere Fallback-Stufen abruft und schnelle Unit-Tests klar von langsamen Integrationstests trennt.

Die Architektur stand damit im Grunde schon fest, bevor die eigentliche Implementierung begann.

Wie Claude Code dabei vorging

Beim Schreiben dieses Artikels habe ich in den Session-Logs im JSONL-Format nachgesehen, was Claude Code an diesem Abend eigentlich genau gemacht hat.

Es lief eine Pipeline aus mehreren Superpowers-Skills:

  1. Die Spezifikation aus dem Konzept-Chat auf claude.ai wurde mit einem Planungs-Skill in nummerierte Aufgaben zerlegt: “Task 1: npm-Dependencies”, “Task 2: URL Normalization Module”, “Task 3: Redirect Resolution” und so weiter bis ungefähr Task 6.
  2. Für jede Aufgabe wurde über subagent-driven-development ein neuer Subagent gestartet. Der bekam nur seine konkrete Aufgabenbeschreibung – nicht die komplette Session und auch nicht die Inhalte der anderen Tasks.
  3. Jeder Implementierungs-Subagent erhielt ausdrücklich die Anweisung, nach TDD zu arbeiten: zuerst einen fehlschlagenden Test schreiben, prüfen, dass er wirklich fehlschlägt, die minimale Implementierung bauen, den Test erneut ausführen und anschließend committen.
  4. Nach jedem Task prüfte ein weiterer Subagent, ob die Umsetzung tatsächlich der Spezifikation entsprach.

In den Logs finden sich die Briefings aus dieser Nacht recht genau:

You are implementing Task 2: URL Normalization Module … with TDD

Es folgte “Task 3: Redirect Resolution”, “Task 5: Markdown Formatting” und “Spec compliance review for Task 6”.

Das war also kein einzelner Geistesblitz von Claude Code, sondern das mechanische Ergebnis mehrerer TDD-Implementierungen mit anschließender Review-Stufe.

Der Skill-Stack

“Tests zuerst” war hier kein zufällig entstandenes Default-Verhalten. Der Workflow hat es ausdrücklich erzwungen.

Wer mit Claude Code ein neues Projekt auf ähnliche Weise aufbauen möchte, braucht dafür im Wesentlichen vier Skills aus dem Superpowers-Plugin:

  • brainstorming klärt vor der Implementierung, was überhaupt gebaut werden soll, welche Einschränkungen gelten und welche Entscheidungen getroffen werden müssen. In meinem Fall hatte der Konzept-Chat auf claude.ai diese Aufgabe bereits übernommen.
  • writing-plans zerlegt die Spezifikation in nummerierte, überschaubare Aufgaben, die sich einzeln umsetzen und testen lassen.
  • subagent-driven-development startet für jeden Task einen frischen Subagenten, der nur den Kontext seiner eigenen Aufgabe erhält.
  • test-driven-development bildet die innere Schleife: fehlschlagender Test, minimale Implementierung, grüner Test, Commit.

Natürlich kann man Claude Code auch einfach sagen: “Schreibe zuerst Tests.”

Formal ist es dasselbe. Aber ohne einen vorher zerlegten Plan fehlen häufig die klaren Grenzen, an denen sinnvolle Tests überhaupt ansetzen können. Der Skill-Stack liefert nicht nur die Anweisung, Tests zu schreiben, sondern auch die Struktur, die ihnen eine konkrete Bedeutung gibt.

Dockerfile, Compose und .dockerignore

Noch in derselben Nacht wanderte der Service in einen Container.

Das Dockerfile verwendet ein normales Node.js-Image. Kein Headless-Browser, kein zweiter Prozess und keine unnötig komplizierte Konstruktion.

Zu diesem Zeitpunkt musste der Service genau drei Dinge können:

  1. Reddit-JSON abrufen
  2. die Daten in Markdown umwandeln
  3. das Ergebnis ausgeben

Dafür reichte ein einzelner schlanker Node.js-Container völlig aus.

Die docker-compose.yml bekam die üblichen Traefik-Labels: eine Subdomain aus dem internen Schema meines Netzes und den automatischen Certificate Resolver.

Der Service läuft auf meinem Ubuntu-Docker-Host hinter Traefik. Traefik übernimmt TLS und routet die Subdomain auf den Container.

Das ist dasselbe Setup, das ich auch für meine anderen selbst gehosteten Dienste verwende: Container starten, Traefik liest die Labels, stellt das Zertifikat aus und der Dienst ist erreichbar.

Die .dockerignore blieb entsprechend klein:

node_modules/
data/
.git/

Mehr war nicht nötig. Der Service hatte keine Build-Artefakte und keinen lokalen Zustand, der in das Image gehört.

Damit entstand ein sauberes Image, das auf dem Host einfach mit docker compose up -d gestartet werden konnte.

Die PWA mit Web Share Target

Die PWA war schon in derselben Nacht dabei. Und genau für diesen Teil hatte ich das Tool ursprünglich gebaut.

Eine Progressive Web App, kurz PWA, ist im Grunde eine normale Webanwendung, die sich auf dem Smartphone wie eine App installieren lässt. Sie bekommt ein eigenes Icon auf dem Startbildschirm, öffnet sich ohne die übliche Browseroberfläche und kann bestimmte Funktionen des Betriebssystems verwenden.

Dafür braucht es keine native Android-App, keinen Play Store und auch keine separate Codebasis. Die vorhandene Weboberfläche wird über ein Manifest um die notwendigen App-Informationen ergänzt und muss über HTTPS erreichbar sein.

Für PullMD war vor allem die Web Share Target API interessant. Damit kann sich eine installierte PWA im Teilen-Menü von Android als Ziel registrieren.

Der entscheidende Teil des Manifests sah so aus:

"share_target": {
  "action": "/share",
  "method": "GET",
  "params": {
    "title": "title",
    "text": "text",
    "url": "link"
  }
}

Teilt man einen Link mit der PWA, übergibt Android den Titel, Text und die URL an den /share-Endpunkt. PullMD nimmt daraus den Reddit-Link, startet automatisch die Extraktion und zeigt anschließend das fertige Markdown an.

In der Praxis bedeutete das:

PWA installieren, Reddit-App öffnen, einen Beitrag aufrufen und auf “Teilen” tippen.

PullMD erscheint anschließend im Android-Share-Menü neben WhatsApp, Signal und den anderen üblichen Verdächtigen.

Ein Tipp auf den Eintrag öffnet die PWA, die Extraktion läuft und kurz darauf steht das Markdown auf dem Bildschirm.

Copy-Button. Fertig.

Der erste Aha-Moment auf dem Handy

Ich weiß noch ziemlich genau, wann es zum ersten Mal vollständig funktioniert hat.

Ich hatte in der Reddit-App einen längeren Thread zu einem interessanten Thema geöffnet, über das ich mit Claude reden wollte. Also auf “Teilen” getippt, den Service ausgewählt – und dann stand da einfach das saubere Markdown.

Keine Navigation, kein Cookie-Banner, keine Sidebar. Nur der Beitrag und die Kommentare, eingerückt nach ihrer jeweiligen Tiefe.

Mein erster Gedanke war nicht einmal: “Cool, es funktioniert.”

Es war eher: Wow, das ist deutlich schneller, als ich erwartet hatte.

Der alte Ablauf war:

URL kopieren, in einen anderen Tab wechseln, die Adresse einfügen und auf das Ergebnis warten.

Jetzt war es ein Tipp im Share-Menü, und das Markdown war da.

Das klingt erst einmal nur nach ein paar eingesparten Handgriffen. In der Praxis ist es aber genau der Unterschied zwischen einem Werkzeug, das man tatsächlich benutzt, und einem, bei dem man jedes Mal denkt: Ach, dafür müsste ich jetzt erst wieder …

Die Tests haben sich später bezahlt gemacht

In den folgenden Wochen wuchsen die Testdateien zusammen mit dem Projekt weiter.

Jedes Mal, wenn wir etwas Größeres am URL-Handling, am Kommentarformat oder an der Fallback-Logik änderten, schlugen zuerst die entsprechenden Tests fehl.

Damit sah Claude direkt, welche bisher funktionierenden Fälle eine Änderung nebenbei beschädigt hätte – bevor sie auf dem Server landete.

Die Test-Suite, die in der ersten Nacht durch den Skill-Stack fast nebenbei entstanden war, bewährte sich damit auch im Alltag.

Gerade weil die Tests nicht erst nachträglich an die fertige Anwendung geklebt wurden, bildeten sie die ursprünglichen Architekturentscheidungen ziemlich genau ab.

Was danach kam

Danach lief der Dienst einfach vor sich hin. Hauptsächlich auf dem Handy und erst mal nur für mich.

Was dabei die ganze Zeit nirgends stand: was man an dem Ding eigentlich aufrufen kann.

→ PullMD Teil 3: Die API, und wie aus Reddit jede URL wurde


Teil 2 von 15 der PullMD Serie.

← PullMD Teil 1: Strg+A und der Junk-Kontext | PullMD Teil 3: Die API, und wie aus Reddit jede URL wurde →

Kommentare

Zum Kommentieren genügt eine E-Mail-Bestätigung oder ein GitHub-Login. Die Kommentare laufen auf einem eigenen Server, nicht bei einem Werbeanbieter — Details in der Datenschutzerklärung.