· Webentwicklung · 7 minuten Lesezeit
Chrome Extension Foundation mit Health-Dot und Retry-Queue
Phase 1 legt das Fundament: Health-Dot, Retry-Queue und Capture History machen die Chrome Extension im täglichen Einsatz zuverlässig und nachvollziehbar.

Inhalt
- Warum Phase 1 keine neuen Features hat
- Die Roadmap zuerst
- userId als Investition in die Zukunft
- Der Health-Dot macht das Backend sichtbar
- Die Retry-Queue für verlorene Captures
- Die Capture History schließt den Feedback-Loop
- Was Phase 1 tatsächlich ist
- Alle Artikel der Serie
Warum Phase 1 keine neuen Features hat
Ich hatte ein lauffähiges System. Die Extension erkannte Instagram-Posts, extrahierte Metadaten, schnitt Screenshots zu und speicherte alles in einer Vektordatenbank. Das RAG-Backend beantwortete Fragen über die gespeicherten Posts.
Dann kam die Frage: Was kommt als nächstes?
Ich hatte sechzehn Feature-Ideen notiert. Carousel-Capture, Multi-Plattform-Support, Tags, Shortcut-Konfiguration, Offline-Queue. Alles interessant. Alles umsetzbar.
Aber bevor ich neue Fähigkeiten baue, muss die Basis stimmen. Eine Extension, die still scheitert, erzeugt kein Vertrauen. Eine Extension, bei der der Nutzer nicht weiß ob das Backend läuft, erzeugt keine Nutzungsgewohnheit. Eine Extension ohne Fehlertoleranz verliert Daten.
Phase 1 heißt deshalb „Foundation & Trust”. Vier Features. Keine davon fügt eine neue Capture-Fähigkeit hinzu. Alle davon machen das System spürbar robuster.
Die Roadmap zuerst
Bevor ich eine Zeile Code schrieb, habe ich eine fünfphasige Roadmap erstellt. Das Prinzip: Jede Phase ist unabhängig auslieferbar. Jede Phase baut auf der vorherigen auf. Schema-Änderungen kommen so früh wie möglich, bevor sie rückwärts-inkompatibel werden.
Die fünf Phasen:
- Chrome Extension Foundation: Health-Dot, Retry-Queue, Capture-History
- Phase 2, Capture Quality: Duplicate Detection, Visual Preview, Shortcut-Konfiguration
- Phase 3, Content Enrichment: Tags, Notizen, Carousel-Capture
- Phase 4, Power und Platform: Offline-Queue, Multi-Plattform, Bulk-Capture
- Phase 5, Scale und Habit: Analytics, Privacy-Blocklist, Export
Die Roadmap liegt als ROADMAP.md im Projekt-Root. Das ist kein Task-Tracking-Dokument. Es ist ein Orientierungsrahmen, der erklärt warum die Features in dieser Reihenfolge kommen, nicht nur was kommt.
Abbildung: Die fünf Phasen der Extension-Roadmap. Jede Phase ist eigenständig auslieferbar. Die Reihenfolge folgt dem Prinzip: erst Vertrauen aufbauen, dann neue Fähigkeiten ergänzen.
userId als Investition in die Zukunft
Das erste Feature aus Phase 1 ist das unauffälligste. Ich habe dem capturedBy-Objekt ein Feld userId: string | null hinzugefügt. Überall: in types.ts der Extension, im Backend und im Frontend.
Aktuell ist der Wert immer null. Kein Auth-System, kein Login. Nur das Feld.
capturedBy: {
username: string;
userId: string | null; // TODO: replace with authenticated provider userId
capturedAt: string;
}Der Grund: Wenn ich später Auth einbaue, muss ich keine Migration der bestehenden Daten in Qdrant vornehmen. Das Feld ist schon da. Es ist null, was semantisch korrekt ist: nicht bekannt. Sobald ein Auth-Provider existiert, wird null durch eine echte ID ersetzt.
Das kostet mich heute eine Zeile pro Datei. Und spart mir morgen eine Breaking Change.
Dieses “später” ist inzwischen umgesetzt. Wie Zitadel als Identity Provider in Docker Compose eingerichtet wird und wie der vollständige PKCE-Flow in der Extension konkret funktioniert, beschreibe ich in den Folgeartikeln der Serie.
Der Health-Dot macht das Backend sichtbar
Ein unsichtbares Problem ist ein ungelöstes Problem.
Wenn das Backend nicht erreichbar ist, scheitert jeder Capture still. Die Extension reagiert nicht. Keine Fehlermeldung. Kein Hinweis. Der Nutzer weiß nicht, ob er selbst etwas falsch macht oder ob der Server down ist.
Der Health-Dot löst das. Im Popup erscheint oben rechts ein kleiner Kreis. Grün bedeutet: Backend erreichbar. Rot bedeutet: kein Kontakt.
Das Popup ruft beim Öffnen GET /health auf, mit einem 4-Sekunden-Timeout.
async function checkHealth(): Promise<void> {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
const res = await fetch(HEALTH_ENDPOINT, { signal: controller.signal });
clearTimeout(timeout);
setHealthStatus(res.ok ? 'online' : 'error');
} catch {
setHealthStatus('offline');
}
}Das ist kein Monitoring. Das ist Feedback. Der Nutzer weiß in unter einer Sekunde, ob er arbeiten kann.
Abbildung: Das Popup der Extension nach Phase 1. Oben der Health-Dot in grün oder rot, darunter die letzten Captures mit relativem Zeitstempel, ganz unten der Retry-Button falls Captures fehlgeschlagen sind.
Die Retry-Queue für verlorene Captures
Captures scheitern manchmal. Das Backend antwortet nicht, das Netzwerk hängt, der Server startet gerade neu. Ohne Fehlerbehandlung sind die Daten verloren.
Die Retry-Queue speichert fehlgeschlagene Payloads in chrome.storage.local. Maximal fünf Einträge. Das Extension-Icon bekommt einen roten Badge mit der Anzahl der wartenden Captures.
async function enqueuePendingCapture(payload: AnalysisPayload): Promise<void> {
const existing = (await getStorage<PendingCapture[]>('pendingCaptures')) ?? [];
const updated = [
...existing.slice(0, 4), // max 5 entries
{ payload, enqueuedAt: new Date().toISOString() },
];
await chrome.storage.local.set({ pendingCaptures: updated });
await chrome.action.setBadgeText({ text: String(updated.length) });
await chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
}Im Popup gibt es einen „Alle wiederholen”-Button. Er holt die Queue, schickt jeden Eintrag erneut und räumt erfolgreich gesendete Einträge auf.
Ein wichtiges Detail: Die Queue greift nur, wenn der Fehler nach dem Aufbau des Payloads passiert. Wenn die Metadaten-Extraktion selbst fehlschlägt, gibt es keinen validen Payload zum Speichern.
Die Capture History schließt den Feedback-Loop
Wann habe ich was captured? Von welchem Channel? Auf welcher Plattform?
Ohne Feedback ist jede Interaktion mit der Extension ein Akt des Glaubens. Du drückst Ctrl+Q, siehst kurz das Overlay, bestätigst. Und dann? Nichts. Du weißt nicht ob es funktioniert hat.
Die Capture History speichert nach jedem erfolgreichen Capture einen Eintrag in chrome.storage.local:
type CaptureHistoryEntry = {
capturedAt: string;
channel: string;
platform: string;
};Im Popup erscheinen die letzten fünf Captures. Mit relativem Zeitstempel: „Vor 3 Minuten”, „Vor 2 Stunden”. Das reicht, um die wichtigste Frage zu beantworten: Hat der letzte Capture funktioniert?
function relativeTime(isoString: string): string {
const diff = Date.now() - new Date(isoString).getTime();
const minutes = Math.floor(diff / 60_000);
if (minutes < 1) return 'Gerade eben';
if (minutes < 60) return `Vor ${minutes} Minute${minutes > 1 ? 'n' : ''}`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `Vor ${hours} Stunde${hours > 1 ? 'n' : ''}`;
const days = Math.floor(hours / 24);
return `Vor ${days} Tag${days > 1 ? 'en' : ''}`;
}Kein externes Package. Keine Formatierungs-Library. Dreißig Zeilen.
Was Phase 1 tatsächlich ist
Die vier Features aus Phase 1 haben keine gemeinsame technische Klammer. Es gibt keinen neuen Service, keine neue Architekturebene.
Was sie gemeinsam haben: Sie machen das System beobachtbar. Der Nutzer sieht den Systemzustand. Der Nutzer sieht was passiert ist. Der Nutzer kann auf Fehler reagieren, ohne Debug-Logs zu öffnen.
Das ist das eigentliche Ziel von Phase 1. Nicht Features. Vertrauen.
Alle Artikel der Serie
Vision und Systemübersicht: Chrome Extension, RAG-Architektur, Projekthintergrund: Artikel lesen
RAG-System Aufbau: Qdrant, Embeddings, Cosine-Ähnlichkeit in TypeScript: Artikel lesen
AI Provider Abstraktion: Ollama vs. OpenAI, Interface-Design, kein Vendor-Lock-in: Artikel lesen
Chrome Extension MV3: Drei isolierte Laufzeitkontexte, Message Passing, Strategy Pattern: Artikel lesen
Docker Compose Strategie: Override-Pattern, von lokal zu Azure: Artikel lesen
Ollama lokal vs. Docker: Die Entscheidung und ihre Konsequenzen: Artikel lesen
Ollama Auto-Pull Entrypoint: Automatisiertes Modell-Setup beim Container-Start: Artikel lesen
tsconfig und Vite:
Node16vs.bundler, warum Vite eigene Regeln hat: Artikel lesenInstagram Caption mit MutationObserver vollständig laden: Artikel lesen
Chrome Extension Foundation mit Health-Dot und Retry-Queue (dieser Artikel)
Phase 2 Features: Shadow DOM Overlay, Tailwind v4, Duplicate Detection: Artikel lesen
Race Condition bei der Plattformerkennung: Wie ein UI-Event die Instagram-Erkennung bricht: Artikel lesen
PostId-Extraktion in zwei Instagram-Layouts: querySelector vs. Ancestor-Traversal: Artikel lesen
Instagram Karussell vollständig erfassen mit MutationObserver: Lazy-Loading, Observer-before-click, Timeout-Fallback: Artikel lesen
Notiz und Tags beim Screenshot-Speichern: Artikel lesen
Instagram Tastatur-Shortcuts blockieren Chrome Extension Eingaben: Artikel lesen
Lowercase-Normalisierung und Duplikat-Erkennung im Tag-Input: Artikel lesen
Zitadel Login V2 in Docker Compose: drei versteckte Fehler: Artikel lesen
PKCE OAuth in einer Chrome MV3 Extension: Artikel lesen
React Frontend mit react-oidc-context und Zitadel: Artikel lesen
Vite Build-Time-Umgebungsvariablen in Docker: Artikel lesen
Event-Driven Ingestion mit BullMQ und Redis: Artikel lesen
MinIO statt Azurite: S3-kompatible Objektspeicherung lokal und auf Hetzner: Artikel lesen
access_token, id_token und der Userinfo-Endpoint: was wohin gehört: Artikel lesen
Qdrant Multi-Tenancy: Pro Nutzer eine eigene Collection: Artikel lesen
Wenn Backend und Frontend unterschiedliche Typen kennen: Artikel lesen
Zitadel Bootstrap entfernt: Host-Header-Bug und manuelles Setup: Artikel lesen
Backend Code Review: sechs Probleme vor dem Launch behoben: Artikel lesen
Traefik statt NGINX: Reverse Proxy für einen wachsenden Docker-Compose-Stack: Artikel lesen
Zweischichtiges Rate Limiting: Traefik und express-rate-limit mit Redis: Artikel lesen
DSGVO Art. 17 korrekt implementieren: Promise.allSettled und Export-Batching: Artikel lesen
Embedding-Modell-Lock-in: Warum mxbai-embed-large eine Produktionsentscheidung für immer ist: Artikel lesen
Docker Volumes in Produktion: Named Volumes, Bind Mounts und der Hetzner-Volume-Trick: Artikel lesen
Zwei Sicherheitslücken vor dem Launch: Redis ohne Auth und ein offener Qdrant-Admin-Port: Artikel lesen
Traefik als einziger Einstiegspunkt im Docker Compose Stack: Artikel lesen
Zitadel hinter Traefik richtig verdrahten mit Issuer, JWKS und Login V2: Artikel lesen
Frontend gesund machen wenn der nginx Healthcheck an localhost scheitert: Artikel lesen
Du planst eine Browser-Extension oder ein System mit Offline-Resilience und Nutzerfeedback? Lass uns das gemeinsam einschätzen.



