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
- Prompt 1 → in einem neuen leeren Lovable-Projekt eingeben. Das wird Projekt A (Master).
- Projekt A remixen → daraus wird Projekt B. Optional nochmal remixen → Projekt C.
- In B (und C) Prompt 2 eingeben, um die Rolle als Peer zu setzen (Name, Peer-URLs).
- In A Prompt 3 eingeben, um die Peer-URLs von B/C einzutragen.
- 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
- Happy Path: In A eine Note anlegen → nach ≤ 2s in B und C sichtbar (Origin-Badge "A").
- 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.
- Konflikt: Dieselbe id in A und B parallel updaten. Nach Sync gewinnt das jüngere
updated_atüberall. - 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 nurSYNC_SECRET(Runtime-Secret) verwenden — niemals alsVITE_*.- 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, niemalsid-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
- Repos verbinden (in jedem Projekt einzeln):
Plus-Menü (+) → GitHub → Connect project → Repo anlegen als
sync-demo-a,sync-demo-b,sync-demo-c. - 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) - Remotes verlinken, damit du zwischen den Repos cherry-picken kannst:
(Alternativ die GitHub-HTTPS-URL von Repo A als Remote hinzufügen.)cd ~/sync-demo/b git remote add master ../a git remote update cd ~/sync-demo/c git remote add master ../a git remote update - 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
- Änderung in Lovable A per Prompt umsetzen (UI, neues Modul, DB-Schema).
- Wenn DB-Änderung: Migration idempotent formulieren (
CREATE TABLE IF NOT EXISTS,ADD COLUMN IF NOT EXISTS,DROP … IF EXISTS). - In A testen. Erst wenn's läuft, weitermachen.
- 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
- 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. - Danach Code veröffentlichen: In Lovable B im Publish-Dialog auf Update klicken (Frontend-Änderungen sind sonst nur im Preview).
- 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:
- Migration in allen drei Projekten ausrollen (A, B, C).
- 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 --continuebun.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:
- Code: In Lovable B/C → Version History → auf den letzten guten Stand zurück. (Löst automatisch einen Commit im Repo aus.)
- 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.
- Sync-State: Bei Bedarf
public.sync_statein 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
-
/demofunktioniert offline / ohne Cloud-Verbindung -
/livezeigt Warn-Badge und schreibt inpublic.live_notes - Reload in
/demoleert Daten, Reload in/livebehält Daten - Realtime: zweiter Browser-Tab in
/livesieht neue Notes ohne Refresh - Migration
live_notesist 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:
- Projekt A remixen → daraus wird Projekt B. Die Cloud-DB von B ist leer.
- In B
VITE_NODE_NAME = "B"setzen,VITE_PEER_URLSauf die stabile URL von A zeigen (https://project--<idA>.lovable.app). - Runtime-Secrets
BACKEND_SHARED_SECRETundSYNC_SECRETmit denselben Werten wie in A hinterlegen. - In A
VITE_PEER_URLSauf die stabile URL von B ergänzen. - 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.