Setup-Prompts

Zum Kopieren: echtes Multi-Projekt-Sync-Setup mit Remix.

Setup-Prompts — Multi-Projekt Sync-Experiment

Ziel: Zwei (oder drei) echte Lovable-Projekte mit eigener DB, die sich per Webhook + Pull-Fallback gegenseitig synchronisieren. Empfohlen: Projekt A einmal frisch bauen, dann remixen → Projekt B/C (identisches Abbild, keine Prompt-Drift).


Zwei Wege

Es gibt zwei Einsatz-Szenarien für diesen Gateway. Wähle den passenden Prompt-Block:

  • Grüne Wiese — du willst eine kleine Sync-Demo von Null aufbauen. Dann nutze Prompt 1 → 2 → 3 unten (Notes-App als Playground).
  • Bestehende App aufrüsten — du hast schon ein Lovable-Projekt mit eigener DB (z.B. KFS) und willst zwei Nodes davon hinter einem eigenen Gateway betreiben. Dann nutze Prompt R + Prompt G unten. Prompt R kommt in die Ziel-App (A und B), Prompt G in den frischen Gateway-Remix.

Der Gateway-Pilot selbst wird pro Ziel-App einmal remixt und davorgeschaltet.


Ablauf

  1. Prompt 1 → in einem neuen leeren Lovable-Projekt eingeben. Das wird Projekt A (Master).
  2. Projekt A remixen → daraus wird Projekt B. Optional nochmal remixen → Projekt C.
  3. In B (und C) Prompt 2 eingeben, um die Rolle als Peer zu setzen (Name, Peer-URLs).
  4. In A Prompt 3 eingeben, um die Peer-URLs von B/C einzutragen.
  5. Shared Secret in allen Projekten als Runtime-Secret hinterlegen (SYNC_SECRET, gleicher Wert überall).

Beispiel-Sync-Secret

YJhIc4WEADznzEUBqs4GzpqAwxXvyoxswr8X_Ro51IZX93HVno20-H8d7BLvOrgG

Dieser Wert ist nur ein Beispiel. Du kannst ihn verwenden, oder in jedem Projekt einen eigenen, identischen Wert als SYNC_SECRET hinterlegen. Wichtig: derselbe Wert muss in allen Peer-Projekten stehen.


Prompt 1 — Projekt A (Master) aufsetzen

Baue mir eine kleine Notes-App mit Lovable Cloud als Sync-Playground.

Anforderungen:

1. Datenmodell (Migration):
   Tabelle public.notes:
   - id uuid primary key default gen_random_uuid()
   - content text not null
   - updated_at timestamptz not null default now()
   - origin text not null   -- z.B. "A", "B", "C" — welches Projekt hat's zuletzt geschrieben
   - deleted boolean not null default false   -- Soft-Delete für Sync
   Grants + RLS: authenticated darf select/insert/update/delete; service_role all.
   Für den Prototyp: RLS-Policies "true" (alle authentifizierten dürfen alles). Anon: kein Zugriff.

2. UI (Startseite):
   - Header zeigt groß den Projektnamen (aus VITE_NODE_NAME, default "A").
   - **Ganz oben, vor allem anderen (kein Scrollen nötig):** kompaktes Chaos-Panel dieser Instanz mit genau zwei Toggle-Schaltern:
     **„App down"** und **„Datenbank nicht erreichbar"** (siehe Punkt 8). Deutlich abgesetzt (roter/oranger Rahmen, Überschrift „⚠️ Chaos-Modus — nur diese Instanz"). Kein weiteres Chaos-Setting im UI.
   - Liste aller Notes (nicht deleted), sortiert nach updated_at desc.
   - Jede Note zeigt: content, origin-Badge, updated_at, Delete-Button (Soft-Delete).
   - Input + "Anlegen"-Button (setzt origin = eigenes NODE_NAME).
    - Direkt unter dem Chaos-Panel die eigene **stabile Projekt-URL** (`https://project--<PROJECT_ID>.lovable.app`, und falls published zusätzlich `https://project--<PROJECT_ID>.lovable.app` — die Preview-Variante `project--<PROJECT_ID>-dev.lovable.app` daneben) groß und kopierbar anzeigen, mit Label:
      **„URL dieses Projekts ({NODE_NAME}) — kopieren und in den *anderen* Projekten unter VITE_PEER_URLS eintragen. Verwende IMMER die `project--<id>.lovable.app`-Variante, NICHT `id-preview--...`, weil die Projekt-ID auch nach Umbenennung stabil bleibt."**
    - Bereich "Peers": Liste der konfigurierten Peer-URLs mit Status (online/offline) und Button "Jetzt syncen".
      - Hier werden nur die URLs der *anderen* Projekte eingetragen. Als Platzhaltertext im leeren Zustand anzeigen:
        **„Trage hier die URLs der anderen Projekte ein. Deine eigene URL gehört NICHT hier rein, sondern umgekehrt in deren VITE_PEER_URLS."**
      - Neben jedem Peer-URL-Eingabefeld ein kleiner Hinweis: **„URL eines *anderen* Projekts im Format `https://project--<id>.lovable.app` (stabile Projekt-ID, nicht `id-preview--...`)"**.
   - Bereich "Sync-Log": letzte 20 Sync-Events (Richtung, Anzahl, Zeit, Fehler).

3. Konfiguration per Env / Secrets:
   - VITE_NODE_NAME  (öffentlich, z.B. "A")
   - VITE_PEER_URLS  (kommagetrennte Basis-URLs, z.B. "https://projekt-b.lovable.app,https://projekt-c.lovable.app")
   - SYNC_SECRET     (Runtime-Secret, geteilt zwischen allen Projekten — HMAC). Beispiel siehe oben.
   - BACKEND_SHARED_SECRET (Runtime-Secret, geteilt mit dem Load-Balancer). Schützt ALLE /api/public/* Routen: nur Requests mit passendem `x-backend-secret`-Header werden bedient. Dadurch können die Backend-Projekte problemlos public published sein, ohne dass Außenstehende die Endpoints aufrufen können.

4. Sync-Endpunkte (TanStack Start Server-Routes unter /api/public/):

   Alle /api/public/* Routen (inkl. /health) prüfen ZUERST den Header
   `x-backend-secret` gegen `process.env.BACKEND_SHARED_SECRET` per
   `crypto.timingSafeEqual`. Fehlt der Header oder passt er nicht → sofort
   `new Response("Unauthorized", { status: 401 })`. Kein Body lesen, kein Log
   mit dem Secret. Einheitlich als kleine Helper-Funktion `requireBackendSecret(request)` in `src/lib/backend-auth.ts`.

   POST /api/public/sync-in
   - Header X-Sync-Signature: HMAC-SHA256(body, SYNC_SECRET), timingSafeEqual.
   - Body: { changes: Note[], from: string }
   - Für jede eingehende Note: upsert per id, nur überschreiben wenn eingehendes updated_at > lokales updated_at (Last-Write-Wins). origin wird übernommen.
   - Antwort: { accepted: number, skipped: number }.

   GET /api/public/changes?since=<iso-timestamp>
   - Header X-Sync-Signature: HMAC über den Query-String.
   - Liefert alle Notes (inkl. deleted=true) mit updated_at > since. Limit 500.

   POST /api/public/health
   - Prüft nur `x-backend-secret` (siehe oben), keine HMAC. Antwortet { ok: true, node: NODE_NAME }.

5. Sync-Mechanismus (Server-Function, vom UI und beim Anlegen/Update/Delete aufgerufen):
   - pushToPeers(): schickt eigene Änderungen seit last_pushed_at an jede Peer /sync-in mit HMAC.
   - pullFromPeers(): ruft bei jedem Peer /changes?since=last_pulled_at auf, mergt per Last-Write-Wins.
   - Nach Insert/Update/Delete lokal automatisch pushToPeers() feuern (fire-and-forget, Fehler loggen, nicht blocken).
   - Zusätzlich: Button "Jetzt syncen" → macht push + pull sequentiell.
   - last_pushed_at / last_pulled_at pro Peer in einer Tabelle public.sync_state (peer_url primary key, last_pushed_at, last_pulled_at) speichern.

6. Loop-Schutz:
   - Beim /sync-in NIEMALS erneut pushToPeers auslösen.
   - origin bleibt beim Merge erhalten (nicht auf eigenes NODE_NAME umschreiben).

7. Doku:
   - Lege eine SYNC.md an, die kurz erklärt: Datenmodell, Endpunkte, HMAC-Signatur (Beispiel-cURL), Konfliktregel, Setup-Schritte.
   - Verlinke SYNC.md im Header ("Sync-Doku").

8. **Chaos-/Fehler-Schalter (läuft auf der Backend-Instanz selbst):**

   Ziel: Auf jedem Backend-Projekt lokal simulieren können, dass die App down
   oder die DB nicht erreichbar ist — ohne den Prozess zu killen. So lässt
   sich der Load-Balancer und der Peer-Sync gegen die zwei wichtigsten
   Failure-Modi durchspielen. Die Schalter leben **ausschließlich in dieser
   Instanz** (in-memory pro Worker) und wirken auf **alle `/api/public/*`
   Routen** dieser Instanz. Kein Persist, kein Sync zu Peers — Reload/Redeploy
   setzt alles auf „normal".

   **Bewusst reduziert auf genau zwei Schalter:** `appDown` und `dbDown`.
   Keine Latenz-, Error-Rate-, ReadOnly-, RejectSync-, DropHealth- oder
   BadSecret-Toggles — die haben sich als überflüssig für die Demo erwiesen.

   a) State-Modul `src/lib/chaos.ts` (server-only, module-scope):
      ```ts
      export type ChaosMode = {
        appDown: boolean;      // liefert 503 „app down" auf allen /api/public/*
        dbDown: boolean;       // Endpunkte, die die DB brauchen, liefern 503 „db down"
        seededAt: number;      // Zeitpunkt der letzten Änderung (für UI)
      };
      // Getter/Setter + resetChaos() + snapshot() exportieren.
      ```

   b) Middleware/Helper `withChaos(request, handler)` — vor **jedem**
      `/api/public/*` Handler aufrufen, direkt nach `requireBackendSecret`:
      - Wenn `appDown` an → `503 {"error":"app down","node":NODE_NAME}`.
      - Bei allen DB-berührenden Routen: wenn `dbDown` an → 503
        `{"error":"db down","node":NODE_NAME}` **ohne** DB-Query.
      Jede simulierte Antwort setzt `X-Chaos: <grund>` Response-Header für Log/Debug.

   c) Steuer-Endpunkte unter `/api/public/chaos/*` (ebenfalls durch
      `requireBackendSecret` geschützt, aber **nicht** durch `withChaos` — sonst
      sperrst du dich aus):
      - `GET  /api/public/chaos`      → aktueller Zustand + `node: NODE_NAME`.
      - `POST /api/public/chaos`      → Body `Partial<ChaosMode>` (nur `appDown`/`dbDown`), setzt Felder,
                                        antwortet mit neuem Zustand.
      - `POST /api/public/chaos/reset`→ setzt beide Schalter auf `false`.
      - `POST /api/public/chaos/panic`→ Kill-Switch: setzt `appDown=true`.
      - `POST /api/public/chaos/heal` → Alias für `reset`, für „schnell wieder
                                        online" im Demo.

   d) UI-Bereich „Chaos-Simulation" **ganz oben** auf der Startseite dieser
      Instanz (erste sichtbare Sektion, kein Scrollen), deutlich abgesetzt
      (roter/oranger Rahmen, Überschrift „⚠️ Chaos-Modus — nur diese Instanz"):
      - Genau zwei Toggle-Schalter nebeneinander: **„App down"** (`appDown`)
        und **„Datenbank nicht erreichbar"** (`dbDown`). Sonst nichts.
      - Button „Alles zurücksetzen" (→ `/chaos/reset`).
      - Live-Anzeige des aktuellen Zustands (aus `GET /chaos`, alle 2s pollen).
      - Prominenter Banner direkt darunter, wenn einer der beiden Schalter
        aktiv ist: „⚠️ Diese Instanz simuliert Fehler: <appDown|dbDown>".
      - Beim `dbDown`-Toggle in der lokalen Notes-Liste einen sichtbaren Hinweis
        „DB simuliert offline — Liste ist Snapshot".

   e) Sicherheit / Nebenwirkungen:
      - Chaos-Zustand **niemals** zu Peers syncen.
      - Wenn `appDown=true` lokal aktiv ist, senden `pushToPeers` und
        `pullFromPeers` keine ausgehenden Sync-Requests (spart Fehler-Logs).
      - Chaos-Endpunkte tauchen im normalen Sync-Log **nicht** auf.
      - Wenn `appDown` gesetzt ist, darf die eigene UI trotzdem laden (die
        Chaos-Panels sind Client-seitig; Steuerung geht über den geschützten
        Chaos-Endpunkt, der `withChaos` bewusst umgeht).

   f) Test-Szenarien (dokumentiere sie in SYNC.md unter „Chaos-Modi"):
      - „App komplett aus" → `appDown=true` in B. Load-Balancer failovert auf A/C,
        Health zeigt B rot. `appDown=false` → B wieder in Rotation.
      - „Nur DB weg" → `dbDown=true` in B. `/health` bleibt grün (nur App), aber
        Notes-Endpunkte antworten 503. Peer-Sync markiert B als „unhealthy DB".

Nutze Lovable Cloud (kein externes Supabase). Halte den Code klein und lesbar.

Prompt 2 — Projekt B (bzw. C), nach Remix von A

Konfiguriere dieses geremixte Projekt als Peer "B":

WICHTIG: In VITE_PEER_URLS gehören NUR die URLs der ANDEREN Projekte, nicht die eigene.

1. Setze VITE_NODE_NAME = "B" (in .env, ohne Anführungszeichen zu belassen — Vite-Env-Format).
2. Hole dir die **stabile Projekt-URL** von Projekt A im Format `https://project--<PROJECT_ID>.lovable.app` (Published) bzw. `https://project--<PROJECT_ID>-dev.lovable.app` (Preview) — diese URLs bleiben auch nach Umbenennung des Projekts konstant. **Nicht** die `id-preview--...`-Variante verwenden. Trage sie ein:
   VITE_PEER_URLS = "<URL von Projekt A>"
   Falls es Projekt C schon gibt, ebenfalls dessen `project--<id>.lovable.app`-URL hinten anhängen, kommagetrennt.
3. Lege das Runtime-Secret SYNC_SECRET mit demselben Wert wie in Projekt A an. Beispielwert (oder dein eigener, identischer Wert): `YJhIc4WEADznzEUBqs4GzpqAwxXvyoxswr8X_Ro51IZX93HVno20-H8d7BLvOrgG`
   Lege zusätzlich BACKEND_SHARED_SECRET mit demselben Wert wie in Projekt A und im Load-Balancer-Projekt an. Ohne diesen Header lehnt B/C alle /api/public/*-Requests mit 401 ab — so darf B/C ruhig public published sein.
4. Ändere nichts an der DB-Struktur oder Sync-Logik — die soll identisch zu A bleiben.
5. Der Chaos-Modus (Prompt 1, Punkt 8) muss identisch vorhanden sein — bei jedem Remix übernommen, in-memory pro Instanz. Prüfe, dass `/api/public/chaos` erreichbar ist und das Chaos-Panel mit den zwei Schaltern **ganz oben** auf der Seite sichtbar ist (ohne Scrollen).
6. Zeige mir am Ende deutlich:
   - die eigene stabile `project--<PROJECT_ID>.lovable.app`-URL dieses Projekts (B), die ich in Projekt A als Peer eintragen muss.
   - die URL(s), die ich gerade unter VITE_PEER_URLS eingetragen habe.
7. Stelle sicher, dass die UI-Hinweise aus Prompt 1 (Punkt 2) vorhanden sind: eigene URL oben groß + kopierbar, und beim Peers-Bereich der Hinweis „Hier gehören die URLs der *anderen* Projekte rein, nicht die eigene". Falls sie fehlen, ergänze sie.

(Für Projekt C analog: VITE_NODE_NAME = "C", VITE_PEER_URLS = "<A>,<B>".)


Prompt 3 — Projekt A: Peers eintragen

Trage in Projekt A unter .env die **stabilen Projekt-URLs** (`https://project--<PROJECT_ID>.lovable.app`) der anderen Projekte ein (NICHT die eigene URL von A, und NICHT die `id-preview--...`-Variante):
VITE_PEER_URLS = "<URL Projekt B>,<URL Projekt C>"

Diese URLs bekommst du, indem du in Projekt B (und C) oben auf der Seite die eigene `project--<id>.lovable.app`-URL kopierst (siehe Prompt 2, Schritt 6). Projekt-IDs sind stabil — auch nach Umbenennung des Projekts ändert sich die URL nicht.

Verifiziere, dass alle Peers unter /api/public/health erreichbar sind, und triggere einmal manuell einen Full-Sync (push + pull) gegen jeden Peer. Zeige mir das Ergebnis im Sync-Log.

Test-Szenarien

  1. Happy Path: In A eine Note anlegen → nach ≤ 2s in B und C sichtbar (Origin-Badge "A").
  2. Offline-Peer: C stoppen (Preview zu). In A + B je eine Note anlegen. C wieder starten, "Jetzt syncen" drücken → beide Notes tauchen mit korrektem Origin auf.
  3. Konflikt: Dieselbe id in A und B parallel updaten. Nach Sync gewinnt das jüngere updated_at überall.
  4. Delete-Propagation: In A eine Note löschen (Soft-Delete). Nach Sync ist sie in B und C verschwunden.

Wichtige Fallstricke

  • VITE_*-Env-Variablen sind clientseitig sichtbar. Für das Shared Secret nur SYNC_SECRET (Runtime-Secret) verwenden — niemals als VITE_*.
  • Projekt-URLs im Format project--<id>.lovable.app (Published) bzw. project--<id>-dev.lovable.app (Preview) sind stabil und ändern sich nicht, wenn das Projekt umbenannt wird — für Peer-Konfig immer diese verwenden, niemals id-preview--... oder umbenennbare Alias-URLs.
  • HMAC-Signatur über den exakten Body-String bilden (nicht über das re-serialisierte JSON), sonst schlägt die Verifizierung fehl.
  • Beim Remix wird die DB nicht kopiert — die neue Instanz bekommt eine leere Postgres. Das ist gewollt.

Rollout-Strategie: Änderungen von A → B → C über GitHub

Ziel: Änderungen (Code, UI, neue Module, DB-Migrations) sauber vom Master in die Peers ausrollen, ohne Prompt-Drift und ohne dass A/B/C auseinanderlaufen.

Grundprinzip

    ┌──────────────┐          ┌──────────────┐
    │  Lovable A   │ ◄──sync──► │   Repo A    │  ← "Master", hier entwickelst du
    └──────────────┘          └──────┬───────┘
                                     │ cherry-pick / PR
                        ┌────────────┼────────────┐
                        ▼                         ▼
    ┌──────────────┐          ┌──────────────┐          ┌──────────────┐
    │  Lovable B   │ ◄──sync──► │   Repo B    │          │   Repo C     │ ◄──sync──► Lovable C
    └──────────────┘          └──────────────┘          └──────────────┘

Jedes Lovable-Projekt ist bidirektional mit seinem eigenen GitHub-Repo verbunden. Änderungen wandern per Git zwischen den Repos, Lovable zieht sie automatisch nach.

Einmaliges Setup

  1. Repos verbinden (in jedem Projekt einzeln): Plus-Menü (+) → GitHub → Connect project → Repo anlegen als sync-demo-a, sync-demo-b, sync-demo-c.
  2. Lokal alle drei Repos klonen in einen gemeinsamen Ordner:
    ~/sync-demo/
      ├─ a/   (git clone …/sync-demo-a)
      ├─ b/   (git clone …/sync-demo-b)
      └─ c/   (git clone …/sync-demo-c)
    
  3. Remotes verlinken, damit du zwischen den Repos cherry-picken kannst:
    cd ~/sync-demo/b
    git remote add master ../a
    git remote update
    
    cd ~/sync-demo/c
    git remote add master ../a
    git remote update
    
    (Alternativ die GitHub-HTTPS-URL von Repo A als Remote hinzufügen.)
  4. Migrations-Ordner in allen Repos identisch halten. Empfehlung: supabase/migrations/ mit strikt aufsteigend nummerierten Dateien (Timestamp-Prefix).

Standard-Workflow für eine Änderung

Phase 1 — im Master (Projekt A) entwickeln

  1. Änderung in Lovable A per Prompt umsetzen (UI, neues Modul, DB-Schema).
  2. Wenn DB-Änderung: Migration idempotent formulieren (CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS, DROP … IF EXISTS).
  3. In A testen. Erst wenn's läuft, weitermachen.
  4. Lovable pusht automatisch nach Repo A. Prüfe: Commit taucht auf GitHub auf.

Phase 2 — Änderung in Repo B (und C) landen

Bevorzugt: eine PR pro logischer Änderung. Zwei Wege, je nach Größe:

Kleiner Change (1–5 Commits): Cherry-Pick.

cd ~/sync-demo/b
git fetch master
git checkout -b sync/<kurzbeschreibung>
git cherry-pick <sha-aus-repo-a>   # ggf. mehrere
# Konflikte lösen, testen (siehe unten), pushen:
git push -u origin sync/<kurzbeschreibung>
# PR auf GitHub öffnen, mergen.

Größerer Change / mehrere zusammenhängende Commits: Range-Merge.

cd ~/sync-demo/b
git fetch master
git checkout -b sync/<kurzbeschreibung>
git merge master/main --no-ff
# Konflikte lösen, testen, pushen, PR, merge.

Phase 3 — Rollout in Lovable B/C

  1. DB-Migration ZUERST: Nach dem Merge in Repo B zieht Lovable B den neuen Code inklusive Migrations-Datei. Prüfe in Lovable B, dass die Migration ausgeführt wurde (Cloud → Database → Migrations / Tables). Falls Lovable sie nicht automatisch erkennt, in Lovable B prompten:
    Führe die neue Migration aus supabase/migrations/<datei>.sql aus.
    
  2. Danach Code veröffentlichen: In Lovable B im Publish-Dialog auf Update klicken (Frontend-Änderungen sind sonst nur im Preview).
  3. Dasselbe für Lovable C.

Phase 4 — Sync verifizieren

  • In A eine Test-Note anlegen, die das neue Feld / Feature nutzt.
  • Auf B und C nachschauen: Note ist da, Feld korrekt gefüllt, kein 500er im Sync-Log.
  • Umgekehrt: in B eine Note anlegen → in A und C prüfen.

Reihenfolge-Regel (kritisch)

Bei jeder Änderung, die DB und Code betrifft:

  1. Migration in allen drei Projekten ausrollen (A, B, C).
  2. Dann Code (Publish/Update) in allen drei Projekten.

Sonst schreibt der neue Code in A eine Spalte, die B/C noch nicht kennen → /api/public/sync-in wirft 500, Sync steht.

Bei rückwärtskompatiblen Änderungen (neue Spalte nullable, neue Tabelle) ist die Reihenfolge weniger kritisch, aber halte dich trotzdem daran — spart Debug-Zeit.

Umgang mit Konflikten beim Cherry-Pick

  • .env / VITE_NODE_NAME / VITE_PEER_URLS: NIE aus A übernehmen — B hat "B" drin, C hat "C". Beim Konflikt immer die Ziel-Seite behalten:
    git checkout --ours .env
    git add .env
    git cherry-pick --continue
    
  • bun.lock / package-lock.json: bei Konflikten neu generieren (bun install), committen.
  • src/routeTree.gen.ts: nie manuell mergen — löschen, Lovable regeneriert:
    rm src/routeTree.gen.ts
    git checkout master/main -- src/routes/
    bun run dev   # regeneriert die Datei
    git add src/routeTree.gen.ts
    

Was NICHT über Git syncbar ist

Diese Dinge musst du pro Projekt separat setzen — Git überträgt sie nicht:

Ding Wo setzen
Runtime-Secrets (SYNC_SECRET) Lovable → Cloud → Secrets, in jedem Projekt
Runtime-Secret BACKEND_SHARED_SECRET Lovable → Cloud → Secrets, in Load-Balancer UND in jedem Backend (identischer Wert)
.env mit VITE_NODE_NAME / VITE_PEER_URLS pro Projekt manuell (unterschiedliche Werte!)
DB-Daten (Notes-Inhalte) läuft über den Sync-Mechanismus zur Laufzeit
Cloud-Instanzgröße, Auth-Einstellungen pro Projekt in Lovable UI

Change-Prompt-Vorlage (für Änderungen direkt in A)

Damit Master-Änderungen möglichst kompakt und cherry-pickbar bleiben:

Ändere Folgendes im Projekt (bitte in einem einzigen zusammenhängenden Commit):

1. [ ] DB-Migration (falls nötig): <konkretes SQL, idempotent formuliert>
2. [ ] Code-Änderung: <welche Datei(en), welches Verhalten>
3. [ ] UI-Änderung: <was soll der User sehen>
4. [ ] Sync-Auswirkung: <ändert sich das Datenformat in /api/public/sync-in?>
       → falls ja: rückwärtskompatibel machen (neue Felder nullable, alte Felder nicht entfernen).

Halte den Diff klein. Keine Umformatierungen an nicht betroffenen Dateien.

Rollback

Wenn ein Rollout in B/C etwas kaputt macht:

  1. Code: In Lovable B/C → Version History → auf den letzten guten Stand zurück. (Löst automatisch einen Commit im Repo aus.)
  2. DB: Migrations sind selten trivial rückgängig zu machen. Deswegen: immer eine Down-Migration mitschreiben oder zumindest im PR-Text notieren, wie man das Schema von Hand zurücksetzt.
  3. Sync-State: Bei Bedarf public.sync_state in B/C leeren, damit der nächste Sync-Zyklus einen Full-Pull macht.

Checkliste pro Rollout

  • Änderung in A lokal getestet
  • Idempotente Migration im Repo, wenn DB betroffen
  • PR in Repo B gemergt, Migration in Lovable B ausgeführt, Publish→Update geklickt
  • Dasselbe für Repo C / Lovable C
  • End-to-End-Test: Note in A anlegen → in B + C sichtbar
  • End-to-End-Test: Note in B anlegen → in A + C sichtbar
  • Sync-Log in allen drei Projekten ohne Fehler

Prompt 5 — Demo- vs Live-Test-Bereich im selben Projekt trennen

Ziel: In diesem Projekt (Gateway Pilot / Master-Erklärstück) zwei klar getrennte Modi haben:

  • Demo-Modus — läuft komplett in-memory im Browser (kein DB-Write). Dient nur dazu, das Konzept live vorzuführen. Reload = alles weg. Kein Cloud-Traffic.
  • Live-Test-Modus — schreibt in eine echte Tabelle in Lovable Cloud (public.live_notes), die sich später gegen echte Peers B/C syncen lässt. Reload = Daten bleiben.

Beide Modi teilen sich UI-Komponenten, aber unterschiedliche Datenquellen/Hooks. Ein Umschalter oben rechts entscheidet, was aktiv ist. Der aktive Modus wird als Badge groß sichtbar angezeigt (Farbe: Demo = neutral/grau, Live = akzent/warn), damit man beim Vorführen nie durcheinanderkommt.

Prompt (in dieses Projekt einfügen)

Erweitere dieses Projekt um eine saubere Trennung zwischen zwei Bereichen: "Demo" und "Live-Test".

1. Routing:
   - /demo         → in-memory Playground (keine DB), so wie bisher
   - /live         → echte Notes gegen Lovable Cloud (public.live_notes)
   - / (Startseite) → Erklärseite mit zwei großen Karten "Demo öffnen" / "Live-Test öffnen"
     und einem Warnhinweis, dass /live echte Daten schreibt.

2. Gemeinsame UI:
   - Header zeigt einen Mode-Badge ("DEMO" grau / "LIVE" farbig-warn) je nach Route.
   - Notes-Liste, Input, Delete-Button sind dieselbe Komponente <NotesPanel dataSource={...} />.
   - dataSource ist ein Hook-Interface: { notes, addNote, deleteNote, isLoading, error }.

3. Demo-Datasource (useDemoNotes):
   - Reiner React-State (useState/useReducer), kein Fetch, kein Cloud-Zugriff.
   - Reload leert alles. Optional: initial 2–3 Beispiel-Notes.

4. Live-Datasource (useLiveNotes):
   - Migration: Tabelle public.live_notes (id uuid pk default gen_random_uuid(),
     content text not null, updated_at timestamptz not null default now(),
     origin text not null default 'local', deleted boolean not null default false).
     Grants: authenticated select/insert/update/delete; service_role all. RLS an, Policies "true" für authenticated.
   - CRUD über den Standard-Supabase-Client (browser). Realtime-Subscription auf live_notes für live Updates.
   - Zeigt zusätzlich einen kleinen "Cloud"-Indikator (verbunden/fehler).

5. Umschalter & Safety:
   - In /live oben eine dismissible Warn-Leiste: "Dieser Bereich schreibt in die echte Datenbank."
   - Delete-Button in /live erfordert Bestätigung (confirm-Dialog).
   - Keine gemeinsamen Storage-Keys zwischen Demo und Live.

6. Vorbereitung für späteren Peer-Sync:
   - live_notes hat bereits die Felder (origin, deleted, updated_at), die Prompt 1 für Sync erwartet.
   - Später kann /live 1:1 zum "Projekt A"-Verhalten aufgerüstet werden, ohne Schema-Bruch.

Wichtig: Demo und Live dürfen sich niemals Daten teilen. Kein Fallback von Live auf Demo bei Fehler — im Fehlerfall in /live einen sichtbaren Error-State zeigen.

Warum diese Trennung

  • Vorführen ohne Risiko: Demo ist reines Anschauungsobjekt, kein Traffic, kein Datenmüll in der DB.
  • Ehrlicher Live-Test: /live verhält sich wie ein echtes Peer (A) — dieselben Feldnamen, dieselbe Semantik. Wenn später B/C angebunden werden, muss an /live nichts umgebaut werden, nur Sync-Endpunkte drankleben (Prompt 1, Punkt 4).
  • Kein "geht bei mir aber in Demo": durch getrennte Datasources fällt sofort auf, wenn eine Änderung nur im In-Memory-Pfad funktioniert.

Checkliste

  • /demo funktioniert offline / ohne Cloud-Verbindung
  • /live zeigt Warn-Badge und schreibt in public.live_notes
  • Reload in /demo leert Daten, Reload in /live behält Daten
  • Realtime: zweiter Browser-Tab in /live sieht neue Notes ohne Refresh
  • Migration live_notes ist idempotent (create table if not exists ...) — bereit für spätere Übernahme in B/C

Prompt R — Bestehendes Projekt zum Sync-Node umbauen

Wörtlicher Prompt, den du in die Ziel-App (z.B. KFS) einsetzt. Erst in Projekt A (dem Original) mit <NODE_NAME>=A ausführen, dann das Projekt remixen → in Projekt B denselben Prompt mit <NODE_NAME>=B ausführen.

Der bestehende Master-Modus der App bleibt unverändert. Er ist und bleibt ein reines Dev-UI-Gate. Alles, was im Master-Modus in DB-Tabellen geschrieben wird, wird durch den Sync automatisch erfasst — weil die Tabellen selbst im Sync-Scope liegen.

Rüste dieses Projekt zu einem Sync-fähigen Backend-Node hinter einem Lovable-Gateway auf.
Wichtig: bestehende Funktionalität, UI, Auth und der Master-Modus bleiben unverändert.
Der Master-Modus wird NICHT angefasst — er ist und bleibt ein reines Dev-UI-Gate.
Alles, was im Master-Modus in DB-Tabellen geschrieben wird, wird durch den Sync
automatisch erfasst, weil die Tabellen selbst im Sync-Scope liegen.

Kontext:
- Vor dieser App steht ein separater Gateway-Load-Balancer (eigenes Lovable-Projekt).
- Es gibt genau zwei Nodes dieser App: A (dieses Projekt) und B (Remix davon).
- Jeder Node hat seine eigene Lovable-Cloud-DB. Kein Shared DB.
- Sync läuft peer-to-peer per HMAC-signierten HTTP-Calls, kein Broker.

Führe folgende Schritte in dieser Reihenfolge aus:

1. Sync-Scope bestimmen
   - Liste alle Tabellen in public.* auf.
   - Schließe fest aus: user_roles, sync_state, sync_log, lb_routes und alles,
     dessen Name auf _audit oder _log endet.
   - Zeig mir die verbleibende Liste zur Bestätigung, bevor du irgendwas migrierst.

2. Idempotente Migration
   Für JEDE bestätigte Tabelle:
   - ADD COLUMN IF NOT EXISTS updated_at timestamptz not null default now()
   - ADD COLUMN IF NOT EXISTS origin     text not null default '<NODE_NAME>'
   - ADD COLUMN IF NOT EXISTS deleted    boolean not null default false
   - BEFORE-UPDATE-Trigger, der updated_at = now() setzt (SET search_path = public).
   - Bestehende Zeilen: updated_at bekommt now(), origin bekommt '<NODE_NAME>',
     deleted bleibt false.

   Neue Tabellen anlegen (Grants + RLS wie üblich, service_role only, KEIN anon):
   - public.sync_state (peer_url text primary key, last_pushed_at timestamptz,
     last_pulled_at timestamptz)
   - public.sync_log (id uuid pk default gen_random_uuid(), ts timestamptz default now(),
     direction text, peer_url text, table_name text, row_count int, error text)

3. Soft-Delete-Umstellung
   - Suche alle Stellen im App-Code, die .delete() gegen eine synchronisierte Tabelle
     aufrufen. Zeig sie mir als Liste. Nach meinem OK stellst du sie um auf:
     .update({ deleted: true, updated_at: new Date().toISOString() })
   - Alle Reads gegen synchronisierte Tabellen bekommen automatisch
     .eq('deleted', false), außer sie werden explizit als "Archiv"-Ansicht markiert.
   - Die Master-Modus-Screens werden analog behandelt — der Master-Modus selbst
     bleibt sichtbar/gated wie bisher.

4. Sync-Endpunkte unter /api/public/* (TanStack Start Server-Routes)
   Alle Routen prüfen als Erstes den Header x-backend-secret gegen
   process.env.BACKEND_SHARED_SECRET per crypto.timingSafeEqual.
   Fehler → 401, kein Body, kein Log des Secrets.
   Helper: src/lib/backend-auth.ts / requireBackendSecret(request).

   a) POST /api/public/health
      Antwortet { ok: true, node: VITE_NODE_NAME }.

   b) POST /api/public/sync-in
      Header X-Sync-Signature: HMAC-SHA256(rawBody, SYNC_SECRET), timingSafeEqual.
      Body: { from: "A"|"B", changes: { [tableName]: Row[] } }
      Für jede Tabelle: upsert per id mit Merge-Regel Last-Write-Wins:
        - lokale Zeile fehlt          → einfügen
        - eingehendes updated_at > lokal → überschreiben
        - sonst                       → verwerfen
      Antwort: { accepted: number, skipped: number, per_table: {...} }.
      NIEMALS von hier aus pushToPeers auslösen (Loop-Schutz).

   c) GET /api/public/changes?since=<iso>&tables=<csv>
      Header X-Sync-Signature: HMAC über den Query-String.
      Liefert pro angefragter Tabelle alle Rows (inkl. deleted=true) mit
      updated_at > since. Limit 500 pro Tabelle.

   d) Chaos-Endpunkte /api/public/chaos, /chaos/reset — nur zwei Toggles:
      appDown, dbDown. Panel ganz oben auf der Startseite, roter Rahmen.

5. Ausgehender Sync
   - Server-Function pushToPeers(): nach jedem Insert/Update/Delete auf einer
     syncbaren Tabelle fire-and-forget an alle in VITE_PEER_URLS aufgelisteten
     Peers /api/public/sync-in senden. Fehler landen NUR in sync_log, blockieren
     die App nicht.
   - Server-Function pullFromPeers(): ruft bei jedem Peer
     /api/public/changes?since=last_pulled_at, mergt via derselben LWW-Regel.
   - Sichtbarer Button "Jetzt syncen" wird an eine bestehende Admin-/Master-
     Modus-Sektion angehängt (was auch immer in dieser App die "Nur-für-Dev"-
     Ecke ist). Er löst push + pull sequentiell aus.
   - Wenn lokal appDown=true → pushToPeers/pullFromPeers senden nichts.

6. Runtime-Secrets (per Lovable-Cloud-Secrets, NICHT als VITE_*)
   - BACKEND_SHARED_SECRET  — identisch in Gateway, A und B
   - SYNC_SECRET            — identisch in A und B (HMAC)
   ENV-Variablen (VITE_*, dürfen public sein):
   - VITE_NODE_NAME         — "A" bzw. "B"
   - VITE_PEER_URLS         — kommagetrennte project--<id>.lovable.app-URLs der
                              ANDEREN Nodes (in A: URL von B; in B: URL von A)

7. Dokumentation
   - Lege SYNC.md an mit: Liste der syncbaren Tabellen, hinzugefügte Spalten,
     HMAC-Beispiel-cURL, Merge-Regel, Chaos-Toggles, Rollback-Hinweise.
   - In SYNC.md klarstellen: "Der bestehende Master-Modus wurde NICHT geändert.
     Alles, was er in DB-Tabellen schreibt, wird durch den Sync erfasst."

8. Zum Abschluss zeig mir:
   - Die stabile URL dieses Projekts: https://project--<PROJECT_ID>.lovable.app
     (NICHT id-preview--..., weil die Projekt-ID stabil bleibt).
   - Die aktuell in VITE_PEER_URLS eingetragenen Peer-URLs.
   - Bestätigung, dass /api/public/health über den Gateway erreichbar ist.

Node B anlegen

Nach dem erfolgreichen Prompt R in Projekt A:

  1. Projekt A remixen → daraus wird Projekt B. Die Cloud-DB von B ist leer.
  2. In B VITE_NODE_NAME = "B" setzen, VITE_PEER_URLS auf die stabile URL von A zeigen (https://project--<idA>.lovable.app).
  3. Runtime-Secrets BACKEND_SHARED_SECRET und SYNC_SECRET mit denselben Werten wie in A hinterlegen.
  4. In A VITE_PEER_URLS auf die stabile URL von B ergänzen.
  5. In A einmal „Jetzt syncen" drücken — B füllt sich mit dem kompletten Stand von A (inkl. Master-Modus-Stammdaten).

Prompt G — Frischen Gateway-Remix konfigurieren

Wörtlicher Prompt für den remixten Gateway-Piloten, der vor die Ziel-App gestellt wird. Pro Ziel-App genau ein eigener Gateway-Remix.

Konfiguriere diesen Gateway-Remix für die Ziel-App "<AppName>":

1. In der Tabelle lb_routes einen Eintrag anlegen:
   host = "<enduser-domain>", targets = "<url-A>,<url-B>"
   URLs im Format https://project--<id>.lovable.app (KEINE id-preview--...).
2. Runtime-Secret BACKEND_SHARED_SECRET auf denselben Wert setzen, den A und B haben.
3. ENV PUBLISHED_BACKEND_URLS = "<url-A>,<url-B>" als Fallback setzen.
4. Sim/Demo-Bereich in der Gateway-UI deaktivieren (falls noch aktiv).
5. Custom-Domain <enduser-domain> in Lovable-Projekt-Settings mit diesem Projekt verbinden.
6. Prüfen: /api/lb/status zeigt beide Backends grün. Chaos-Toggle appDown in A
   → Traffic wandert transparent auf B.