Files
videogen/planung/programmier-plan.md
2026-08-15 13:43:52 +02:00

19 KiB
Raw Permalink Blame History

Programmier-Plan

Stand: 28. Juli 2026 Repo: git.webklar.com/knso/videogen · Backend: Appwrite appwrite.webklar.com/v1, Projekt 6a5cee34002bb8360c34, DB brandloop Grundlage: app-aufbau.md (Screens) · konzept-bilder-feed.md (Konzept) · datenbank-aufbau.md (Schema)


1. Ausgangslage was schon da ist

Stand
Appwrite-Schema 22 Tabellen, Indizes, 5 Buckets, Team internal live und vollständig, scripts/setup-appwrite.mjs reproduziert diesen Stand idempotent (Stand 14.08.2026)
Prompts P1P18 als 22 Zeilen geseedet (P8 = Kern + 4 Adapter) via scripts/seed-prompts.mjs, versioniert
Regeln 4 globale Brand-Regeln in rules
Prototyp prototyp-app.html, 21 Screens, Handy-Ansicht, vollständig ohne Backend
App-Code existiert noch nicht es gibt einen Expo-QR-Code, aber keine Anwendung

Nachtrag 14.08.2026 E0 ist erledigt. Der Abschnitt unten beschreibt den Stand vom 28.07. und ist ab hier Historie, keine Aufgabenliste mehr. Die Live-Datenbank war bereits vollständig migriert (alle 7 neuen Tabellen, alle 4 Enum-Erweiterungen, attributes aufgetrennt, 5. Bucket) nur setup-appwrite.mjs im Repo hing auf dem alten Stand hinterher und hätte beim Ausführen die Score-Spalten in attributes wieder angelegt. Das Skript ist inzwischen auf den Live-Stand gezogen und gegen ihn geprüft: 0 angelegt, 251 existierten schon, 0 Warnungen. Auch die beiden Enum-Entscheidungen aus §7.1 (Nischen-Liste, typ_tags) sind getroffen und stehen als NISCHEN und TYP_TAGS im Skript.


2. Schema-Delta was konkret fehlt

Abgeglichen gegen scripts/setup-appwrite.mjs.

2.1 Neue Tabellen (6)

Tabelle Zweck
folders Wissens-Scope: brand_id, name, theme_md, zweck, startwert_modus, ist_default, post_count, signal_count
attribute_scores Score je Attribut je Ordner Herzstück
posts Slot-Rezept, slot_summary, sichtbarkeit, feed_score
post_images Bilderkette, typ = motiv | text_overlay
post_folders m:n Post ↔ Ordner, ist_fremd, quelle
post_metrics Views, Likes, Kopien, Social-Reichweite

2.2 Neue Tabelle wegen des Folgen-Konzepts (1)

Tabelle Zweck
follows follower_brand_id, followed_brand_id, unique auf beide, Index in beide Richtungen

2.3 Änderungen an bestehenden Tabellen

Tabelle Änderung Art
brands + nische, + plan, + anzeigename, + avatar_file_id, + follower_count, + ist_oeffentlich Spalten ergänzen
attributes score, k_factor, start_value, used_count, wins, losses, last_used_at → wandern nach attribute_scores; Index idx_topn entfällt hier Umbau
categories + folder_id Spalte + Index
assets + nische, + typ_tags[], + feed_score, + feed_score_updated_at, + ist_teilbar; typ-Enum um kulisse Spalten + Enum-Änderung
score_events + folder_id, + post_id; event_type-Enum um feed_import, post_reichweite Spalten + Enum-Änderung
scenes + folder_id Spalte
jobs typ-Enum um slot_analyse, ordner_vorschlag, werbetext Enum-Änderung
prompt_templates key-Enum P18 → P22 (P_KEYS im Skript: length: 1822) Enum-Änderung

2.4 Neuer Bucket

generated-images öffentlich lesbar, sobald der zugehörige Post veröffentlicht ist.

2.5 Neue Functions (4)

ordner-initialisieren · feed-score-berechnen (Cron) · feed-import · social-reichweite-holen (Cron) Dazu die aus dem alten Plan noch offenen: elo-update, favorit-berechnen, ads-metriken-holen, kategorie-durchschnitt, archivierung, job-dispatcher.

2.6 Zwei Fallen bei der Migration

Enum-Änderungen sind kein Anhängen. Vier Spalten brauchen neue Enum-Werte (assets.typ, score_events.event_type, jobs.typ, prompt_templates.key). Appwrite behandelt das als Spaltenänderung, nicht als Ergänzung je nach Version bedeutet das Löschen und Neuanlegen der Spalte, also Datenverlust. Das ist der Grund, warum diese Änderung jetzt passieren muss, solange die Tabellen leer sind. In sechs Wochen mit echten Daten ist es ein Migrationsprojekt.

Die attributes-Auftrennung ist ein Umbau, kein Zusatz. Sieben Spalten verschwinden aus einer Tabelle und tauchen in einer neuen wieder auf, mit einer zusätzlichen Dimension. Solange keine Brand existiert, ist das eine Textänderung im Setup-Skript. Danach ist es eine Datenwanderung mit Ausfallzeit.

Deshalb ist „Fundament zuerst" nicht nur die aufgeräumte, sondern die einzig günstige Reihenfolge.


3. Etappen

Jede Etappe hat ein Abnahmekriterium etwas, das man vorführen kann. Ohne das ist nicht entscheidbar, ob sie fertig ist.

E0 · Schema nachziehen erledigt (14.08.2026)

Inhalt: setup-appwrite.mjs um die 7 neuen Tabellen, die geänderten Spalten, die Enum-Erweiterungen und den Bucket ergänzen. attributes auftrennen. P_KEYS auf 22. Abnahme erfüllt: 22 Tabellen und 5 Buckets stehen live; das Skript läuft ohne eine einzige Änderung durch (0 angelegt, 251 existierten schon, 0 Warnungen). Datenbestand: nur Stammdaten 4 Zeilen rules, 22 Zeilen prompt_templates (P1P18, P8 als Familie). Keine Brand-Daten. Enum-Änderungen sind ab jetzt trotzdem nicht mehr gefahrlos, weil prompt_templates.key gefüllt ist.

Lehre aus dieser Etappe: Die Datenbank war der Quelle voraus, nicht umgekehrt. Wer setup-appwrite.mjs in diesem Zustand ausgeführt hätte, hätte die sieben Score-Spalten in attributes neu angelegt und die Auftrennung halb zurückgedreht ein idempotentes Skript schützt nur vor doppeltem Anlegen, nicht vor einer veralteten Definition. Deshalb: nach jeder Schema-Änderung an der Konsole das Skript nachziehen und mit scripts/check-appwrite.mjs gegenprüfen.

E1 · Projektgerüst Expo erledigt im Browser (14.08.2026), nativ ungeprüft

Inhalt: Expo-Projekt in client/, TypeScript, Expo Router (Stack + Tabs), Appwrite-SDK verdrahtet, Theme aus dem Prototyp. Fake-Tastatur und setInterval-Shader sind bewusst nicht übernommen. Abnahme: npx tsc --noEmit fehlerfrei · npx expo export --platform web erzeugt alle drei Routen · im Browser rendern /, /erstellen und /profil mit Tab-Leiste, keine Konsolen-Fehler. Offen: der Start auf einem echten Gerät dafür fehlt ein Testgerät bzw. ein Dev-Build.

Struktur (client/src/):

Pfad Inhalt
app/_layout.tsx Wurzel-Stack, nicht direkt die Tabs Willkommen/Anmelden liegen laut app-aufbau.md §3 vor den Tabs, das Onboarding als Modal darüber. Beide brauchen eine Ebene ohne Tab-Leiste.
app/(tabs)/ Feed · Erstellen · Profil
lib/appwrite.ts + .web.ts plattform-getrennter Client. react-native-appwrite läuft nicht im Browser, das Web-SDK appwrite kennt kein React Native beide exportieren dieselben Klassen, der Rest der App importiert nur @/lib/appwrite.
lib/config.ts Endpoint, Projekt-ID, DB-ID aus app.jsonexpo.extra.appwrite. Kein Server-Key der landet sonst im Bundle.
theme/tokens.ts Farben, Radien, Abstände aus prototyp-app.html. Dark-only, weil der Prototyp kein Light-Theme hat.

Zwei bewusste Abweichungen: Das ist vorerst ein normaler Tab statt des Popovers aus §3 der kommt mit den drei Erstellen-Abläufen in E6. Und der Profil-Screen zeigt provisorisch Endpoint und Projekt-ID, damit belegt ist, dass der Client wirklich lädt; das fliegt in E2 raus.

E2 · Auth und Mandant erledigt (14.08.2026)

Inhalt: Willkommen/Registrieren/Anmelden/Abmelden, Session-Persistenz, bei der Registrierung Team + brands-Zeile mit Zeilenrechten. Abnahme erfüllt: scripts/test-mandanten.mjs legt zwei echte Konten an und prüft über die API: B listet nur die eigene Zeile · Direktzugriff B→A 404 row_not_found · Schreibzugriff B→A 401 user_unauthorized · Gegenprobe, dass der Server-Key beide Zeilen sieht. Räumt sich selbst auf.

Der eigentliche Inhalt war das Rechte-Modell, nicht die Formulare. Zwei Dinge, die vorher niemandem aufgefallen waren:

  1. Kein Client konnte irgendetwas anlegen. Bei rowSecurity: true regeln Zeilenrechte lesen/ändern/löschen aber eine Zeile, die es noch nicht gibt, hat keine Rechte. Ohne Tabellen-Recht zum Anlegen war jede Client-Schreiboperation blockiert. 16 Tabellen haben jetzt create("users"); score_events, video_metrics, post_metrics und usage_records bewusst nicht sonst könnte ein Nutzer seine eigenen Elo-Werte und Feed-Signale fälschen (§11, „Manipulation des Feed-Scores").
  2. setup-appwrite.mjs hat Rechte nie abgeglichen. Bei bestehenden Tabellen lieferte POST nur ein 409, die Rechte blieben unangetastet. Das Skript vergleicht jetzt und zieht per PUT nach sonst driftet das Rechte-Modell genauso wie zuvor das Schema.

Nebenfund: SDK-Version passte nicht zum Server. Appwrite läuft in 1.8.1, create-expo-app hatte SDKs mit Response-Format 1.9.5 gezogen. Gepinnt auf appwrite@23.0.0 und react-native-appwrite@0.25.0 (beide Response-Format 1.8.0), exakt statt Caret der Sinn der Pinnung ist ja gerade der Gleichstand mit dem Server.

Das ist die Etappe, bei der man am ehesten schludert und es am teuersten wird. Die Mandantentrennung ist das Fundament des Rechte-Modells aus datenbank-aufbau.md §5.

E3 · Onboarding

Inhalt: Die vier Onboarding-Screens mit echten Feldern, inklusive Nische. Uploads in den Bucket uploads. P2 als erster echter LLM-Aufruf über jobs. Abnahme: Nach dem Onboarding stehen in brands sechs ausgefüllte Felder und in attribute_scores die ersten Attribute auf der brand-weiten Ebene (folder_id = null). Aufwand: mittel.

E4 · Modelle 🟡 Kern steht (14.08.2026), Verbessern-Strecke offen

Inhalt: Modell anlegen (Person, Produkt, Kulisse), echte Referenzbild-Uploads, Versionierung mit Release-Prinzip, Modelle-Übersicht im Profil. Erledigt: Anlegen mit Typwahl und Mehrfach-Bildauswahl (modell/neu), Upload nach asset-references mit Team-Rechten, Version 1 wird angelegt und sofort freigegeben, assets.released_version_id zeigt darauf. Übersicht im Profil nach Typ gruppiert, mit Titelbild aus der freigegebenen Version. Belegt: Vier Modelle (2 Produkte, 2 Kulissen) über scripts/seed-demo.mjs als Client angelegt das beweist nebenbei, dass die Tabellen- und Bucket-Rechte aus E2 ausreichen. Alle vier Bilder laden in der App in Originalgröße. Offen: weitere Versionen anlegen und freigeben (Verbessern-Strecke), merkmale als Token-Lock, Einwilligungs-Upload für Personen.

E5 · Ordner erledigt (14.08.2026)

Inhalt: Ordner-Liste, Anlegen mit den zwei Schaltern, Ordner-Detail, aktiver Ordner als App-Zustand. Function ordner-initialisieren. Abnahme erfüllt nach dem Anlegen über die Oberfläche steht in attribute_scores:

Ordner Modus Scores start_quelle
(brand-weit, folder_id = null) 12 neutral
Sommerkampagne erben 12 geerbt
Cleane Studioshots aus_posts 0
Herbstlinie (über die App angelegt) erben 12 geerbt

Zweck und Startwerte sind als Auswahl mit Erklärung umgesetzt, nicht als nackter Schalter: wer sie missversteht, baut sich einen Ordner, der nicht tut, was er erwartet.

Beim Erben wird der Wert kopiert, aber nicht die Sicherheit k_factor geht zurück auf 32, weil im neuen Scope noch nichts belegt ist.

Abweichung: ordner-initialisieren läuft vorerst im Client statt als Appwrite-Function. Vertretbar, weil nur eigene Zeilen kopiert werden und die Zeilenrechte das ohnehin begrenzen; beim Umzug in eine Function ändert sich nur der Ort.

E6 · Post erstellen 🟡 Rezept-Hälfte steht (14.08.2026), Generierung extern blockiert

Inhalt: Create-Screen mit Ordner, Format, Kette, Slots. jobs + job-dispatcher + Realtime-Wartezustand. P7 auf das Slot-System umbauen. P22 als Bild-Tagger. Post-Ergebnis mit privat/veröffentlichen.

Erledigt: erstellen/post mit Ordnerwahl in der Kopfzeile, Format, Kettenlänge und Slot-Auswahl für Kulisse, Produkt und Person als Bildkacheln, weil bei Kulissen und Produkten das Aussehen die Information ist und ein Name wie „Halle, Metallwand" nichts darüber sagt, ob es passt. Der Post entsteht sofort mit status: entwurf, bevor irgendetwas generiert wird; ohne diese Zeile gäbe es keinen Ort, an den man nach dem Warten zurückkehrt. Je Kettenbild eine jobs-Zeile. Ordner-Detail zeigt die Posts mit aufgelösten Slot-Namen.

Nachweis über die Oberfläche: Post „Serum auf Waschtisch" → Ordner Herbstlinie, 4:5, 3 Bilder, privat, Slots Halle-Metallwand + Refine-&-Renew-Serum, dazu 3 Jobs bild_gen/wartend mit prompt_template_key = P7.

Job-Dispatcher steht scripts/job-dispatcher.mjs, serverseitig, weil der Anbieter-Schlüssel nicht ins Client-Bundle darf. Er baut aus Slot-Rezept, den Beschreibungen der freigegebenen Asset-Versionen, den Top-Attributen des gewählten Ordners und den globalen Regeln einen Prompt, erzeugt das Bild, legt es in generated-images mit Team-Rechten ab, schreibt post_images und setzt den Post auf generiert, sobald die Kette vollständig ist.

Drei Anbieter über BILD_ANBIETER:

Wert Ergebnis (geprüft 14.08.2026)
stub erzeugt Platzhalterbilder die ganze Kette ist damit prüfbar, ohne einen Cent auszugeben
ark ModelNotOpen: account 3003959567 has not activated seedream-5-0
openrouter Insufficient credits. This account never purchased credits

Durchgespielt mit stub: Post „Serum auf Waschtisch" → 3 Jobs → 3 Bilder erzeugt, hochgeladen, Post auf generiert, Kette im Ordner-Detail sichtbar (3/3 geladen). Bei den echten Anbietern greift der Fehlerpfad: Job auf fehler mit der Anbieter-Meldung, Post auf fehler.

Damit fehlt nur noch die Freischaltung. Sobald eines der beiden Konten offen ist, liefert BILD_ANBIETER=ark node scripts/job-dispatcher.mjs echte Bilder ohne Codeänderung.

Offen: Umzug des Dispatchers in eine Appwrite-Function (läuft jetzt lokal/per Cron), P7 als versionierter Prompt statt der Vorstufe im Skript, P22 als Tagger, Wartezustand mit Realtime, Ergebnis-Screen mit Veröffentlichen.

E7 · Feed

Inhalt: Veröffentlichen mit Zeilen-Permission, Feed-Deck mit den drei Reitern, Post-Detail, post_metrics schreiben, Function feed-score-berechnen mit Decay und Explorations-Slot. Abnahme: Ein veröffentlichter Post von Konto A erscheint im Feed von Konto B. prompt_sent ist über die API von Konto B nicht abrufbar. Aufwand: groß.

E8 · Kopieren

Inhalt: P19 (Slot-Analyst), Kopier-Screen mit Chips und Kompatibilitätsprüfung, P21 (Werbetext), Function feed-import, copied_from_post_id, Kopien-Zähler. Abnahme: Konto B kopiert den Post von Konto A, ersetzt beide Modelle, generiert und in der Knowledge Base von Konto B stehen danach neue Attribute mit start_quelle = feed_import im gewählten Ordner. Aufwand: groß. Das ist der Moment, in dem das Produkt das tut, was es verspricht.

E9 · Folgen

Inhalt: follows, Fremdprofil, Folgen-Button, Reiter „Folge ich" chronologisch, Blockieren und Melden. Abnahme: Konto B folgt Konto A und sieht dessen neuen Post im Reiter „Folge ich", vor allem anderen. Aufwand: mittel.

E10 · Video-Strecke

Inhalt: Die bestehende Video-Strecke aus dem Prototyp echt machen: scenes, P3/P4/P5/P8, videos, Vote, elo-update, gezielte Frage. Abnahme: Ein Prompt erzeugt vier Videos von vier KI-Modellen; nach dem Vote haben sich Scores in attribute_scores messbar verändert und score_events hat die passenden Zeilen. Aufwand: sehr groß, und mit laufenden Kosten ab dem ersten Test.


4. Reihenfolge und Abhängigkeiten

E0 Schema
 └─ E1 Gerüst
     └─ E2 Auth ──────────────┐
         ├─ E3 Onboarding     │
         ├─ E4 Modelle ───────┤
         └─ E5 Ordner ────────┤
                              └─ E6 Post erstellen
                                    ├─ E7 Feed
                                    │    ├─ E8 Kopieren
                                    │    └─ E9 Folgen
                                    └─ E10 Video

E3, E4 und E5 sind untereinander unabhängig sie können in beliebiger Reihenfolge oder parallel entstehen. Alles ab E6 hängt an allen dreien.

E10 (Video) steht bewusst hinten, obwohl es das Kernprodukt ist: Es ist die teuerste Strecke in der Umsetzung und im Betrieb, und der Bild-Strang erzeugt vorher die Daten, mit denen sich der Video-Teil überhaupt sinnvoll testen lässt.


5. Was im ersten Durchgang bewusst wegbleibt

Weggelassen Warum
Desktop-Ansicht Handy zuerst; der Handoff im Repo ist laut dir noch nicht gültig
Stripe / Abrechnung Preismodell ist offen (offener Punkt 3) ohne Entscheidung nicht baubar
Meta-/TikTok-Ads-Anbindung App-Reviews dauern; Antrag trotzdem jetzt stellen, unabhängig vom Code
Social-Reichweite (echte Instagram-Zahlen) eigene App-Review; bis dahin läuft der Feed-Score nur auf In-App-Signalen
P12P18, P20 nachrüstbar, ohne sie funktioniert der Loop
Verbessern-Strecke vorhanden im Prototyp, aber nicht auf dem kritischen Pfad
Developer-Modus intern, kein Nutzerwert erst wenn es echte Prompts zu debuggen gibt

6. Risiken

Risiko Wirkung Gegenmaßnahme
Enum-Migration nach dem Livegang Datenverlust in vier Spalten E0 vor allem anderen, solange die DB leer ist
Multi-Referenz-Komposition (Person + Produkt + Kulisse konsistent in einem Bild) Der Kern des Bild-Modus funktioniert nicht wie gedacht Vor E6 mit zwei, drei Kandidatenmodellen einen Wegwerf-Test fahren bevor UI dafür gebaut wird
Generierungskosten im Test schleichende Kosten ohne Gegenwert jobs.cost_usd ab E6 mitschreiben und ein hartes Tageslimit einbauen
Leerer Feed E7 ist nicht bewertbar, E8 nicht testbar Vor E7 mit dem eigenen Konto 1520 Posts erzeugen
Prototyp als Vorlage überschätzt Screens werden 1:1 übernommen inklusive der Attrappen app-aufbau.md §6 ist die verbindliche Liste, nicht die HTML-Datei
Öffentlich/privat nur im UI durchgesetzt fremde Knowledge Bases werden über die API auslesbar Abnahme von E7 ausdrücklich über die API prüfen, nicht über den Bildschirm

7. Was vor E0 zu entscheiden ist

  1. Nischen-Liste und typ_tags-Liste entschieden und live. 20 Nischen, 17 Motiv-Tags, stehen als NISCHEN und TYP_TAGS in scripts/setup-appwrite.mjs.
  2. Bildmodell blockiert nicht E0, aber E6. Der Anbieter steht (OpenRouter, siehe projekt-uebersicht.md §9); offen ist nur, welches der dortigen Bildmodelle die Multi-Referenz-Komposition trifft. Der Wegwerf-Test dafür sollte parallel zu E1E5 laufen.
  3. Mindestalter und Moderationsweg blockiert nicht den Code, aber E7 im Livebetrieb.