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).


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").
   - 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).
    - Ganz oben auf der Seite die eigene Preview-URL (und falls vorhanden Publish-URL) dieses Projekts groß und kopierbar anzeigen, mit Label:
      **„URL dieses Projekts ({NODE_NAME}) — kopieren und in den *anderen* Projekten unter VITE_PEER_URLS eintragen".**
    - 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 (z.B. https://id-preview--... .lovable.app)"**.
   - 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,
   die DB weg, langsam, fehlerhaft oder überlastet ist — ohne den Prozess zu
   killen. So lässt sich der Load-Balancer und der Peer-Sync gegen alle
   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".

   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"
        readOnly: boolean;     // Writes (POST/PATCH/DELETE inkl. /sync-in) liefern 503
        latencyMs: number;     // künstliche Verzögerung vor jeder Antwort (0 = aus)
        errorRate: number;     // 0..1, Anteil zufälliger 500er vor Handler-Ausführung
        rejectSync: boolean;   // /sync-in + /changes liefern 503 (Sync-Ausfall isoliert testen)
        dropHealth: boolean;   // /health liefert 503 statt 200 (Health-Check „rot")
        badSecret: boolean;    // simuliert Secret-Mismatch → 401 auf allen /api/public/*
        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 `badSecret` an → 401 zurück (unabhängig vom echten Secret).
      - Wenn `appDown` an → `503 {"error":"app down","node":NODE_NAME}`.
      - Wenn `latencyMs > 0` → `await new Promise(r => setTimeout(r, latencyMs))`.
      - Wenn `errorRate > 0` und `Math.random() < errorRate` → `500 {"error":"chaos"}`.
      - Für `/health`: wenn `dropHealth` an → 503.
      - Für `/sync-in` und `/changes`: wenn `rejectSync` an → 503 „sync disabled".
      - Bei Write-Methoden auf DB-Routen: wenn `readOnly` an → 503 „read-only".
      - 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>`, setzt Felder,
                                        antwortet mit neuem Zustand.
      - `POST /api/public/chaos/reset`→ setzt alles auf Default (alles aus,
                                        latencyMs=0, errorRate=0).
      - `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" unten auf der Startseite dieser Instanz,
      deutlich abgesetzt (roter/oranger Rahmen, Überschrift „⚠️ Chaos-Modus
      — nur diese Instanz"):
      - Toggle-Schalter für jedes Boolean-Feld (appDown, dbDown, readOnly,
        rejectSync, dropHealth, badSecret).
      - Slider/Number-Input für `latencyMs` (0–5000 ms) und `errorRate` (0–1
        in 0.05-Schritten, als Prozent anzeigen).
      - Button „Alles zurücksetzen" (→ `/chaos/reset`).
      - Button „Panic (App down)" (→ `/chaos/panic`).
      - Live-Anzeige des aktuellen Zustands (aus `GET /chaos`, alle 2s pollen).
      - Prominenter Banner ganz oben auf der Seite, wenn **irgendein** Chaos-
        Feld aktiv ist: „⚠️ Diese Instanz simuliert Fehler: <Liste aktiver
        Modi>". So sieht man beim Vorführen sofort, warum eine Anfrage failed.
      - 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. `pushToPeers` und
        `pullFromPeers` respektieren `rejectSync` lokal (kein Aufruf senden,
        wenn eigener `rejectSync=true`) — 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".
      - „Langsame Instanz" → `latencyMs=2000` in C. LB-Timeout / Retry-Verhalten
        sichtbar, Health-Latency-Anzeige klettert.
      - „Flaky" → `errorRate=0.3` in A. Sync-Log zeigt Retries, LB fällt
        temporär auf gesundes Backend zurück.
      - „Split-Brain" → `rejectSync=true` in B, weiter lokal schreiben. Nach
          Heilung (`heal`) mergen die divergenten Notes per Last-Write-Wins.
      - „Falsches Secret" → `badSecret=true` in C. Alle Peer-Calls und LB-Health
        gegen C liefern 401, C isoliert sich sichtbar.

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 Preview-URL von Projekt A (oben im Lovable-Editor unter "Preview" oder im Browser, z.B. https://id-preview--... .lovable.app) und trage sie ein:
   VITE_PEER_URLS = "<URL von Projekt A>"
   Falls es Projekt C schon gibt, ebenfalls dessen Preview-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 die UI-Panel unten auf der Seite sichtbar ist.
6. Zeige mir am Ende deutlich:
   - die eigene Preview-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 Preview-URLs der anderen Projekte ein (NICHT die eigene URL von A):
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 URL kopierst (siehe Prompt 2, Schritt 5).

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_*.
  • Preview-URLs sind stabil unter project--<id>.lovable.app — für Peer-Konfig immer diese verwenden, nicht die vollen 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