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

PullMD Teil 2: Vom Spec zum laufenden Container

SPEC → TESTS FIRST → CONTAINER · RUNNING
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.

Der Spec aus Teil 1 stand am Vormittag des 2. März. Architektur durchdacht, API-Design klar, Kommentar-Format entschieden. Ich habe den Spec an Claude Code weitergegeben mit der Erwartung, dass es einen Express-Server aufzieht, die Reddit-Fetch-Logik implementiert und dann irgendwann auch einen Test schreibt. Was stattdessen passierte: Claude Code schrieb zuerst vier Test-Dateien. Kein Server, keine Reddit-Logik, kein Express. Vier Tests. Dass die Tests zuerst kamen, war weder Zufall noch Magie — ich arbeite schon länger mit den superpowers-Skills, Claude Code war also kein Neuland. Bemerkenswert ist trotzdem, warum es so konsequent test-first lief.

Vier Tests vor dem ersten Stück Produktionscode

In einer guten halben Stunde am späten Abend des 2. März standen vier Test-Files im Repo, bevor eine einzige Zeile Reddit-Logik existierte. Sie deckten genau das ab, was später die Architektur des Tools ausmachen sollte: URL-Normalisierung als eigene Concern, der Reddit-JSON-Fetch über Mocks, ein HTML-Fallback-Parser für den Fall dass die JSON-Route blockt, und ein separater Integration-Test, der nur auf Anforderung echte Reddit-Calls macht.

Was diese vier Files zusammen beschreiben, ist kein „Tool, das Reddit holt“. Es ist ein Tool, das Reddit in mehreren Fallback-Stufen holt, das URLs zuverlässig normalisiert bevor es anfängt, und das schnelle Unit-Tests von langsamen Integration-Tests trennt. Die Tests legen die Architektur fest — bevor eine Zeile Implementation steht.

Was tatsächlich gelaufen ist

Beim Recherchieren für diesen Artikel habe ich in den Backup-Logs nachgeschaut, was Claude Code in jener Nacht eigentlich gemacht hat. Das Bild ist konkreter als „Claude Code entschied sich für Tests zuerst“ — und nützlicher für den Leser.

Der Workflow, der das Ergebnis erzeugt hat, war eine Pipeline aus mehreren ineinandergreifenden superpowers-Skills:

  1. Der Spec aus dem claude.ai-Konzept-Chat vom Vormittag wurde in Claude Code mit einer Plan-Skill in nummerierte Tasks zerlegt — „Task 1: npm-Dependencies“, „Task 2: URL Normalization Module“, „Task 3: Redirect Resolution“, und so weiter bis ungefähr Task 6.
  2. Pro Task wurde dann ein frischer Subagent dispatched (subagent-driven-development-Skill). Jeder Subagent kriegt nur die eigene Task-Beschreibung als Kontext — nicht den Rest der Session, nicht andere Tasks.
  3. Jeder Implementer-Subagent wurde explizit angewiesen, „following TDD“ zu arbeiten — d.h. der innere Loop ist test-driven-development: failing Test schreiben, verifizieren dass er failed, minimale Implementation, verifizieren dass der Test grün wird, committen.
  4. Nach jedem Task läuft ein Spec-Compliance-Reviewer-Subagent, der prüft ob die Implementation tatsächlich dem Plan entspricht.

In den Backup-Logs sieht man die ersten Implementer-Briefs aus diesem Abend wörtlich: „You are implementing Task 2: URL Normalization Module … with TDD“ um 21:52 CEST, „Task 3: Redirect Resolution“ um 21:58, „Task 5: Markdown Formatting“ um 22:04, „Spec compliance review for Task 6“ um 22:12. Vier Tests-Files-mtimes zwischen 21:53 und 22:26 — das ist nicht ein einziger Geistesblitz, sondern das mechanische Resultat von vier parallelen TDD-Implementer-Runs plus Reviewer-Stage.

Der Skill-Stack hinter dem Pattern

„Tests zuerst“ war hier kein emergentes Default-Verhalten, sondern eine erzwungene Workflow-Eigenschaft. Wer Claude Code für ein neues Projekt nutzt und denselben Effekt haben will, kommt mit einem Stack aus vier Skills aus dem superpowers-Plugin:

  • brainstorming klärt vor jeder Implementation: was wird gebaut, welche Constraints, welche Tradeoffs. In meinem Fall hat der Spec-Chat auf claude.ai diese Rolle übernommen — der Output war detailliert genug, um direkt als Plan-Input zu dienen.
  • writing-plans zerlegt den Spec in nummerierte, in sich abgeschlossene Tasks, die einzeln beschreibbar und testbar sind.
  • subagent-driven-development orchestriert: pro Task ein frischer Subagent, dessen Kontext nur die Task-Beschreibung enthält. Das verhindert, dass Claude beim Implementieren von Task 5 versehentlich auf inkonsistenten Annahmen aus Task 2 aufbaut.
  • test-driven-development ist die innere Schleife pro Task: failing Test → minimale Implementation → grüner Test → Commit.

Wer vor diesem Workflow steht und denkt „Tests zuerst kann ich auch ohne diese Skills sagen“ — stimmt formal. Aber ohne die Plan-Struktur hat Claude Code keine Anker, an denen es Tests sinnvoll formulieren kann. Der Skill-Stack erzwingt die Struktur, an der die Tests dann ihre Bedeutung erhalten.

Die genauen Test-Filenamen, mtime-Sequenzen und die kompletten Subagent-Briefs aus jener Nacht stehen als historische Notiz in meinem Vault — für diesen Artikel reicht der Workflow-Bogen, das Detail würde den Lesefluss erschlagen.

Dockerfile, Compose, .dockerignore (04. März)

Zwei Tage später, am 4. März (09:30, mtime des .dockerignore), wandert der Service in einen Container. Das Dockerfile ist ein Standard-Node.js-Image, nichts Besonderes — kein Headless-Browser, kein zweiter Prozess, ein einziger schlanker Container. Zu diesem Zeitpunkt braucht der Service genau das: Reddit-JSON fetchen, in Markdown umwandeln, ausgeben. Dafür reicht Node.js.

Die docker-compose.yml bekommt Traefik-Labels: eine Subdomain im internen Schema meines Netzes, automatischer Cert-Resolver. Der Service läuft auf meinem Ubuntu Docker Host hinter Traefik, der das TLS terminiert und die Subdomain routet. Das ist das Setup, das ich für alle meine selbst-gehosteten Services nutze — Traefik nimmt einen neuen Container, liest die Labels, stellt ein Zertifikat aus, fertig.

Der .dockerignore ist klein: node_modules/, data/, .git/. Mehr braucht es nicht. Der Service hat keine Build-Artefakte, keinen lokalen State, der ins Image gehört. Ein sauberes Image, das auf dem Host per docker compose up -d startet.

Die PWA mit Web Share Target (08. März)

Sechs Tage nach dem ersten Container-Deploy kommt die PWA-Schicht — und das ist der Teil, für den ich das Tool eigentlich gebaut habe.

Am 8. März, 16:37, landen icon-192.png und icon-512.png auf dem Server. Die Icons hatten einen auffälligen Theme-Color: Reddit-Orange, das kräftige Orange aus dem Reddit-Logo. Rückblickend verräterisch — das Tool war zu dem Zeitpunkt tatsächlich noch ein reines Reddit-Werkzeug, und die Farbe war der stille Beweis dafür. Drei Stunden später, 19:52, folgt favicon.ico.

Das manifest.json selbst bekommt seine mtime erst am 20. März — aber der share_target-Block war konzeptionell vom ersten Tag an geplant, er steckte schon im Spec. Was die spätere mtime zeigt, ist eine Überarbeitung, nicht die Ersterstellung. Der entscheidende Eintrag:

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

Was das praktisch bedeutet: PWA installieren, Reddit-App öffnen, einen Post aufrufen, auf „Teilen“ tippen. Der Service erscheint im Android Share Sheet — neben WhatsApp, Signal, den üblichen Verdächtigen. Klick auf den Eintrag, der Browser öffnet die PWA mit dem Reddit-Link bereits im Query-Parameter, die Extraktion läuft, Markdown erscheint auf dem Bildschirm. Copy-Button. Fertig.

Der erste Aha-Moment auf dem Handy

Ich erinnere mich genau an den Moment, als es das erste Mal durchging. Reddit-App, ein langer Thread über ein Problem, mit dem ich arbeitete. „Teilen“ angetippt, der Service als Ziel gewählt, kurze Pause — und dann sauberes Markdown auf dem Bildschirm. Keine Navigation, kein Cookie-Banner, keine Sidebar. Nur der Post, die Kommentare, eingerückt nach Tiefe.

Der Gedanke, der dann kam, war nicht „cool, es funktioniert“. Es war eher: das ist so viel schneller als ich dachte. Der alte Workflow war: URL kopieren, in einen anderen Tab wechseln, die URL einfügen, auf Ausgabe warten. Jetzt war es: einen Tap im Share-Sheet, und das Markdown ist da. Der Unterschied klingt nach einer Kleinigkeit. Aber in der Praxis ist das der Unterschied zwischen einem Tool, das man nutzt, und einem, das man immer wieder vergisst.

Die Tests haben sich später ausgezahlt

In den folgenden Wochen sind die Test-Files weiter mitgewachsen. Jedes Mal, wenn ich etwas Größeres geändert habe — URL-Handling, Kommentar-Format, Fallback-Logik — sind zuerst die Tests kaputt gegangen. Das hat mir gezeigt, was sich rückwirkend gebrochen hätte, bevor es auf dem Server landete. Die Test-Suite, die in jener ersten Nacht durch den Skill-Stack fast als Nebenprodukt entstanden war, hat sich im Alltag bewährt — gerade weil sie nicht erst nachträglich angeklebt wurde, sondern die Architektur-Entscheidungen direkt mitformuliert hat.

Was als nächstes kam

Drei Wochen lief der Service still — nur für Reddit, nur auf dem Handy, nur für mich. Dann, am Vormittag des 20. März, schrieb ich einen Satz in eine Session, der das Projekt aus seinem Käfig befreit hat. → PullMD Teil 3: Mehr als Reddit, mehr als Readability


Teil 2 von 9 der PullMD Serie.

PullMD Teil 1: Strg+A und der Junk-Kontext | PullMD Teil 3: Mehr als Reddit, mehr als Readability