mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:01:09 +02:00
Brücke von Wardrobe nach Comic: User klickt auf einem Outfit oder
einem einzelnen Kleidungsstück „Als Comic-Character", landet im
Character-Builder mit pre-filltem Add-Prompt ("wearing the
Bühnenoutfit"), picked Stil und rendert die ersten 4 Varianten.
Wardrobe-Buttons:
- DetailOutfitView: unterhalb des TryOnButton ein outline-Link
navigiert zu `/comic/character/new?title=…&prompt=wearing+the+
OUTFITNAME+outfit`.
- DetailGarmentView: analog mit `prompt=wearing+GARMENTNAME` für
ein einzelnes Kleidungsstück. Beide nur sichtbar wenn das
Outfit/Garment nicht archiviert ist.
- Sparkle-Icon + dezent neutraler Border-Style (nicht primary —
das ist die TryOn-CTA), hover schaltet auf primary/40.
Comic CharacterBuilder bekommt drei optionale Props:
`initialName?`, `initialAddPrompt?`, `initialStyle?`. Im
extend-Modus ignoriert (Source ist dann der existing-Character),
im create-Modus dienen sie als $state-Initialwerte. Routine read
ist intentional — Mounting passiert frisch pro Route-Visit, also
einmaliges Capture passt.
`/comic/character/new/+page.svelte` parsed jetzt
`page.url.searchParams` für `title`, `prompt`, `style` und reicht
sie als Props durch. style wird gegen die VALID_STYLES-Liste
validiert — defekte URL-Params fallen ohne Crash auf
"unset/default" zurück.
Bewusst NICHT gemacht: Try-On-Output direkt als sourceBodyMediaId
verwenden. Das Try-On-Bild ist im mana-media mit `app='picture'`
getaggt; `verifyMediaOwnership` auf
`/picture/generate-with-reference` akzeptiert nur
`['me','wardrobe','comic']` — der Comic-Generate würde mit
HTTP 404 abbrechen. Lösung wäre eine Server-Route die Picture-
Output als Comic-Asset re-tagged, das ist aber eigene Spec.
Aktueller Pfad ist sauberer: rohe meImages-Refs bleiben Source,
der Add-Prompt steuert den Outfit-Look.
Plan-Doc §11 Mc5 dokumentiert den Pfad + warum kein
Try-On-Reuse.
Comic-Files type-checken sauber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
732 lines
36 KiB
Markdown
732 lines
36 KiB
Markdown
# Comic — Module Plan
|
||
|
||
## Status (2026-04-24, vor M1)
|
||
|
||
**Geplant, noch nichts geshipped.** Dieses Dokument legt Datenmodell, UI und
|
||
KI-Integration fest; die Meilensteine M1–M5 bringen das Feature auf
|
||
Produktions-Qualität, M6+ sind Ausbau.
|
||
|
||
## Ziel
|
||
|
||
Ein Nutzer erzeugt aus sich selbst und beliebigen Text-Inputs (Tagebuch,
|
||
Notizen, Writing-Drafts, Library-Einträge, Kalender-Events) einen **Comic**
|
||
— eine geordnete Folge von Bild-Panels in konsistentem Stil. gpt-image-2
|
||
rendert jedes Panel aus einer Referenz-Komposition (Face-Ref + optionale
|
||
Szene) und einem Panel-Prompt; Sprechblasen und Caption-Text werden
|
||
direkt ins Bild reinrendered, kein separater Overlay-Layer.
|
||
|
||
Kernfragen, die dieser Plan beantwortet:
|
||
|
||
1. Wie bilden wir eine Comic-Story im Datenmodell ab — als Liste
|
||
geordneter Panel-Referenzen oder als eigenständige Entität?
|
||
2. Wie fließt Input aus anderen Modulen (Journal-Eintrag, Notes,
|
||
Library-Review, Writing-Draft) in die Panel-Generierung ein?
|
||
3. Wie halten wir Character-Konsistenz über Panels hinweg, ohne ein
|
||
separates Character-Management-System zu bauen?
|
||
4. Wie integrieren wir gpt-image-2 mit den fünf unterschiedlichen
|
||
Comic-Stilen (comic/manga/cartoon/graphic-novel/webtoon), ohne pro
|
||
Stil einen eigenen Backend-Pfad zu bauen?
|
||
|
||
## Abgrenzung
|
||
|
||
- **Kein eigener Image-Editor**: Panels sind `picture.images`-Rows wie
|
||
alle anderen generierten Bilder. Wer Panel X nachbearbeiten will,
|
||
tut das im Picture-Modul (oder generiert neu). Comic verwaltet die
|
||
*Reihenfolge und den Story-Kontext*, nicht die einzelnen Pixel.
|
||
- **Kein Storyboard-Canvas in M1–M4**: Panels leben in einer geordneten
|
||
Liste mit optionaler Caption. Ein Comic-Strip-Canvas mit
|
||
Drag-und-Drop-Positionierung (wie Picture-Boards) ist M6+.
|
||
- **Keine SVG-Speech-Bubble-Overlays**: Sprechblasen/Captions werden
|
||
gpt-image-2 über den Prompt reingekippt, nicht nachträglich über SVG
|
||
aufs Bild gelegt. Weniger Kontrolle, einfacher Datenweg, ein
|
||
Asset-Export pro Panel.
|
||
- **Keine eigene Character-DB**: Character-Referenzen sind
|
||
`meImages`-Einträge (Face-Ref, Body-Ref, plus optionale
|
||
Costume-Referenzen aus `wardrobe`). Kein neues Konzept
|
||
"Comic-Character" als eigene Table.
|
||
- **Kein Multi-Character-Crew in M1–M5**: Ein Comic hat *einen*
|
||
Protagonisten (der Nutzer oder eine Kostüm-Variante von ihm). Crew
|
||
mit mehreren Gesichtern ist M6+ — braucht Konsistenz-Tricks, die
|
||
wir nicht auf den MVP-Weg packen wollen.
|
||
- **Cross-Link zu `picture`**: Panel-Ergebnisse landen in
|
||
`picture.images` wie jede andere Generierung. `LocalImage` bekommt
|
||
einen `comicStoryId`-Back-Ref + optional `comicPanelIndex`.
|
||
- **Cross-Link zu `me-images`**: Ohne `useImageByPrimary('face-ref')`
|
||
kein Comic — identisch zu Wardrobe's Try-On-Flow.
|
||
|
||
## Entscheidungen
|
||
|
||
### 1. Ein Modul, eine Tabelle
|
||
|
||
Im Gegensatz zu Wardrobe (Garments + Outfits) reicht für Comic **eine**
|
||
Tabelle:
|
||
|
||
- **`comicStories`** — eine Comic-Story mit Titel, Stil, Character-Refs,
|
||
Story-Kontext, Panel-Liste (als `panelImageIds: string[]` in Plaintext)
|
||
|
||
Kein zweites Table `comicPanels`, weil ein Panel kein eigenständiges
|
||
Primitiv ist — es ist ein `picture.images`-Eintrag mit Back-Ref. Das
|
||
spart Sync-Volumen, vermeidet FK-Cleanup beim Löschen, und hält die
|
||
Panel-Reihenfolge an *einem* Ort (im Story-Record als ID-Array, statt
|
||
als `orderIndex`-Feld auf jedem Panel).
|
||
|
||
Die zusätzlichen Panel-Metadaten (Caption-Text, Dialogue-Vorschläge vom
|
||
AI-Storyboard, Prompt-Varianten) wandern in einen nested-JSON-Feld auf
|
||
der Story:
|
||
|
||
```typescript
|
||
panelMeta: Record<string /* panelImageId */, {
|
||
caption?: string; // freitext, encrypted
|
||
dialogue?: string; // freitext, encrypted
|
||
promptUsed?: string; // encrypted — reproduce/regenerate
|
||
sourceInput?: { // ref auf Cross-Modul-Input für dieses Panel
|
||
module: 'journal' | 'notes' | 'library' | 'writing' | 'calendar';
|
||
entryId: string;
|
||
};
|
||
}>
|
||
```
|
||
|
||
Das ist denormalisiert-aber-handhabbar: wer eine Story löscht, löscht
|
||
automatisch die Meta; wer ein Panel löscht, muss aus `panelImageIds`
|
||
+ `panelMeta` den Eintrag rausnehmen. Trivialer Store-Helper.
|
||
|
||
### 2. Character-Konsistenz via fixe Referenz-Liste pro Story
|
||
|
||
Jede Story speichert bei Erstellung einmal `characterMediaIds: string[]`
|
||
— Face-Ref + optional Body-Ref + optional Kostüm-Fotos aus Wardrobe.
|
||
Alle Panel-Generierungen übergeben diese Referenz-Liste unverändert an
|
||
`/api/v1/picture/generate-with-reference`. gpt-image-2 ist nicht
|
||
deterministisch, aber identische Refs + identischer Stil-Preset-Prefix
|
||
im Prompt ergeben über 4–8 Panels einen *erkennbaren* Character.
|
||
|
||
Kein Feinschliff-Tuning in M1–M5. Wenn sich nach M3 zeigt, dass Panels
|
||
auseinanderdriften, adressieren wir das mit einer zusätzlichen
|
||
"Anchor-Panel"-Referenz (erstes erzeugtes Panel wird Referenz für alle
|
||
folgenden) — das ist M6+.
|
||
|
||
### 2.1 Image-Modell als Picker, nicht hartcodiert (nachgezogen)
|
||
|
||
Comic nutzt die gleiche Model-Auswahl wie Wardrobe's Try-On:
|
||
|
||
- `openai/gpt-image-2` — Default, mittlerer Preis, fällt server-seitig
|
||
auf gpt-image-1 zurück wenn die OpenAI-Org nicht verified ist.
|
||
- `google/gemini-3-pro-image-preview` — Nano Banana Pro, hohe
|
||
Charakter-Konsistenz, höherer Preis.
|
||
- `google/gemini-3.1-flash-image-preview` — Nano Banana 2, neuestes,
|
||
schnell, günstig.
|
||
|
||
`PanelModelPicker` (Analog zu `TryOnModelPicker`) sitzt als
|
||
segmentierter Picker in PanelEditor / BatchPanelEditor /
|
||
StoryboardSuggester. Die Wahl ist per-Editor-Mount lokal; keine
|
||
Story-Level-Persistierung, weil ein Model-Flag auf der Row eine
|
||
Migration bräuchte und die Wahl meistens eh ad-hoc ist.
|
||
|
||
MCP-Tool `comic.generatePanel` und Catalog-Tool `generate_comic_panel`
|
||
akzeptieren beide einen optionalen `model`-Parameter mit demselben
|
||
Enum. Default bleibt `openai/gpt-image-2`.
|
||
|
||
### 3. Fünf Stil-Presets, Mapping im Client
|
||
|
||
```typescript
|
||
export type ComicStyle =
|
||
| 'comic' // US-Comic, Linework + Cell-Shading, kräftige Farben
|
||
| 'manga' // S/W, Screen-Tones, dynamische Perspektiven
|
||
| 'cartoon' // weicher, pastellig, Saturday-Morning-Cartoon
|
||
| 'graphic-novel' // realistischer, Aquarell/Painterly, stimmungsvoll
|
||
| 'webtoon'; // vertikal-lesbar, moderne Farbpalette, Soft-Shading
|
||
```
|
||
|
||
Pro Stil ein Prompt-Prefix-Template im Client (`lib/modules/comic/styles.ts`),
|
||
das in jede Panel-Generierung eingewoben wird. Das Backend kennt die
|
||
Stile *nicht* — es sieht nur den finalen Prompt. Gleicher Ansatz wie
|
||
Wardrobe's `accessoryOnly`-Prompt-Detection.
|
||
|
||
Stil wird bei Story-Erstellung gewählt und ist danach fix. Stil-Wechsel
|
||
= neue Story (oder Panels einzeln neu generieren).
|
||
|
||
### 4. Sprechblasen & Captions direkt im Bild
|
||
|
||
gpt-image-2 kann Text ins Bild rendern — nicht perfekt, aber für
|
||
Comic-Panels akzeptabel. Vorteil: ein einziger Asset-Export pro Panel,
|
||
kein zweiter Overlay-Layer, kein extra Canvas-Render-Schritt beim
|
||
Teilen/Drucken. Nachteil: Text-Korrekturen erfordern Neu-Generierung
|
||
des Panels (= neuer Credit-Call).
|
||
|
||
Im Panel-Editor gibt's zwei Freitext-Felder neben dem Prompt:
|
||
**"Caption"** (Off-Voice-Erzähltext) und **"Dialog"** (Sprechblasen-
|
||
Inhalt). Beide werden in den Prompt eingewoben: `…, caption reading
|
||
"[caption]", character saying "[dialog]" in speech bubble, …`.
|
||
Deutsch-Text funktioniert; User-Erwartungshaltung aber auf
|
||
Englisch-Text einstellen (die Modelle sind auf Englisch stabiler) und
|
||
im UI-Hint vermerken.
|
||
|
||
Der Nutzer kann Caption und Dialog leer lassen → stummes Panel.
|
||
|
||
### 5. Panel-Generierung in drei Modi (evolvierend über M2–M4)
|
||
|
||
- **M2 Single-Panel**: User klickt "+ Panel", schreibt Prompt + optional
|
||
Caption/Dialog, drückt "Generieren". Kosten: 1 gpt-image-2-Call
|
||
(Default `quality='medium'`, 10 Credits).
|
||
- **M3 Batch**: User schreibt 2–4 Panel-Prompts im Voraus, drückt
|
||
"Alle generieren". Backend bekommt `n=1` pro Panel, aber UI startet
|
||
die Calls parallel. Kosten: N × Credits.
|
||
- **M4 AI-Storyboard**: User wählt einen Input (Journal-Eintrag,
|
||
Notes, Writing-Draft, Library-Review, Calendar-Event), Claude liest
|
||
den Text und schlägt 4–6 Panel-Beschreibungen vor (Text-Only,
|
||
kein Bild). User bestätigt/editiert, dann läuft Batch-Gen.
|
||
Claude-Call läuft client-side über bestehende `@mana/shared-ai`
|
||
Helper (kein neuer Service-Pfad nötig).
|
||
|
||
### 6. Cross-Modul-Input: lesend, nicht schreibend
|
||
|
||
Das Comic-Modul *liest* aus den Stores anderer Module (`journal`,
|
||
`notes`, `library`, `writing`, `calendar`), schreibt aber niemals
|
||
dorthin zurück. Ein Journal-Eintrag bleibt im Journal, ein
|
||
Library-Review bleibt in der Library — Comic merkt sich nur per
|
||
`panelMeta[id].sourceInput` dass dieses Panel aus Input X entstanden
|
||
ist. Das erlaubt später "zeig mir alle Comics, die aus diesem
|
||
Journal-Eintrag entstanden sind" als einfache Query.
|
||
|
||
Das Decrypt läuft client-side via `<module>Store.getEntry(id)` →
|
||
`decryptRecords(…)` → übergeben an Claude. Keine Server-Side-Decrypts,
|
||
keine Key-Grants, kein Mission-Flow nötig — weil der Nutzer selbst
|
||
interaktiv am UI steht.
|
||
|
||
### 7. Space-scoped Katalog, user-scoped Protagonist
|
||
|
||
Wie bei Wardrobe: **`comicStories` sind space-scoped** (Brand kann
|
||
Comics über sein Produkt machen, Club über Vereinsgeschichte, Family
|
||
über Kinder-Abenteuer, Team über Bühnenproduktion, Practice als
|
||
Patienten-Aufklärungs-Comic). **Face-Refs bleiben user-global** aus
|
||
`meImages` — wer in einem Brand-Space einen Comic erstellt, ist selbst
|
||
der Protagonist.
|
||
|
||
Family-Edge-Case: Kinder haben keinen eigenen Account, also auch keine
|
||
`meImages`. Wer eine Kinder-Geschichte als Comic machen will, nutzt
|
||
entweder ein eigenes Face-Ref ("Opa erzählt aus dem Krieg, gerendert
|
||
als Opa") oder das Comic-Modul zeigt den Family-Space-Hinweis (analog
|
||
zu Wardrobe): "Protagonist-Rendering nutzt deine eigenen
|
||
Referenzbilder." Kein Multi-Subject-Konzept in M1–M5.
|
||
|
||
Alle sechs Space-Typen bekommen `comic` in die Allowlist.
|
||
|
||
### 8. Visibility-System von Anfang an
|
||
|
||
Comics sind ein Format das Nutzer möglicherweise teilen wollen
|
||
("mein 4-Panel-Comic zum gestrigen Bug-Report"). Wir adoptieren das
|
||
Visibility-System (`shared-privacy`) von M1 an — `visibility`,
|
||
`visibilityChangedAt/By`, `unlistedToken`, `<VisibilityPicker>` im
|
||
Detail-View. Comics mit `visibility='public'` können später via
|
||
`/embed/comic/:id` auf Webseiten eingebettet werden (Plan-Punkt von
|
||
`visibility-system.md` passt 1:1).
|
||
|
||
## Architektur-Überblick
|
||
|
||
```
|
||
┌─ Client (SvelteKit) ────────────────────────────────────┐
|
||
│ /comic │
|
||
│ ListView: alle Stories (Cards mit erstem Panel) │
|
||
│ /comic/[id] │
|
||
│ Detail: Story-Meta + Panel-Strip (horizontal) │
|
||
│ "+ Panel" CTA, pro Panel Caption/Dialog-Editor │
|
||
│ /comic/new │
|
||
│ CreateForm: Titel, Stil, Character-Picker, Kontext │
|
||
│ Dexie: comicStories │
|
||
└──────┬──────────────────────────────────────────────────┘
|
||
│ mana-sync (encrypted title/description/panelMeta)
|
||
▼
|
||
┌─ Panel-Generierung (reuses M3 /picture endpoint) ───────┐
|
||
│ POST /api/v1/picture/generate-with-reference │
|
||
│ referenceMediaIds = story.characterMediaIds │
|
||
│ prompt = stylePrefix + panelPrompt + captionHint │
|
||
│ Result → picture.images row │
|
||
│ Client writes: image.comicStoryId = story.id │
|
||
│ image.comicPanelIndex = N │
|
||
│ story.panelImageIds.push(imageId) │
|
||
│ story.panelMeta[imageId] = {...} │
|
||
└─────────────────────────────────────────────────────────┘
|
||
|
||
┌─ AI-Storyboard (M4, client-side Claude) ────────────────┐
|
||
│ User selects input (journal entry / note / …) │
|
||
│ decryptedText = moduleStore.getEntry(id).content │
|
||
│ Claude.suggest({ style, text }) → Panel[] │
|
||
│ User reviews/edits panels │
|
||
│ Batch-Gen via /picture endpoint │
|
||
└─────────────────────────────────────────────────────────┘
|
||
|
||
┌─ MCP / Agent tools ─────────────────────────────────────┐
|
||
│ comic.listStories (read) │
|
||
│ comic.createStory (write) │
|
||
│ comic.generatePanel (write — consumes credits) │
|
||
│ comic.reorderPanels (write) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Datenmodell
|
||
|
||
### `LocalComicStory`
|
||
|
||
```typescript
|
||
export type ComicStyle =
|
||
| 'comic'
|
||
| 'manga'
|
||
| 'cartoon'
|
||
| 'graphic-novel'
|
||
| 'webtoon';
|
||
|
||
export interface ComicPanelMeta {
|
||
caption?: string; // encrypted
|
||
dialogue?: string; // encrypted
|
||
promptUsed?: string; // encrypted
|
||
sourceInput?: { // plaintext refs
|
||
module: 'journal' | 'notes' | 'library' | 'writing' | 'calendar';
|
||
entryId: string;
|
||
};
|
||
}
|
||
|
||
export interface LocalComicStory extends BaseRecord {
|
||
id: string;
|
||
title: string; // encrypted
|
||
description?: string | null; // encrypted
|
||
style: ComicStyle; // plaintext enum
|
||
/**
|
||
* Referenz-Liste die für jedes Panel-Generate identisch übergeben wird.
|
||
* Mindestens der primary face-ref aus meImages; optional body-ref +
|
||
* bis zu 3 Wardrobe-Garment-Fotos für ein Kostüm-Setup. Cap 8 wie bei
|
||
* Wardrobe (MAX_REFERENCE_IMAGES im /generate-with-reference endpoint).
|
||
*/
|
||
characterMediaIds: string[]; // plaintext FKs
|
||
/**
|
||
* Kontext den Claude in M4 als Briefing für die Storyboard-Generierung
|
||
* sieht. Freitext, typisch 1–3 Sätze ("Ich ärgere mich über einen Bug
|
||
* in unserer Sync-Logik — mach daraus einen 4-Panel-Frust-Comic.").
|
||
*/
|
||
storyContext?: string | null; // encrypted
|
||
/**
|
||
* Geordnete Liste der Panel-picture.images-IDs. Reihenfolge = Lese-
|
||
* reihenfolge. Reorder = neu schreiben.
|
||
*/
|
||
panelImageIds: string[]; // plaintext FKs
|
||
panelMeta: Record<string, ComicPanelMeta>; // keyed by panel image id
|
||
tags: string[]; // encrypted
|
||
isFavorite?: boolean;
|
||
isArchived?: boolean;
|
||
visibility?: VisibilityLevel;
|
||
visibilityChangedAt?: string;
|
||
visibilityChangedBy?: string;
|
||
unlistedToken?: string;
|
||
}
|
||
```
|
||
|
||
**Encryption-Registry-Eintrag:** `['title', 'description', 'storyContext',
|
||
'tags', 'panelMeta']` — `panelMeta` komplett encrypted (JSON-Blob,
|
||
der Freitext-Felder enthält). Style-Enum, IDs, Booleans, visibility
|
||
bleiben plaintext.
|
||
|
||
### Erweiterung auf `picture.images`
|
||
|
||
Zwei neue optionale Plaintext-Felder:
|
||
|
||
```typescript
|
||
// apps/mana/apps/web/src/lib/modules/picture/types.ts
|
||
interface LocalImage {
|
||
// ... bestehend
|
||
wardrobeOutfitId?: string | null;
|
||
wardrobeGarmentId?: string | null;
|
||
comicStoryId?: string | null; // NEU
|
||
comicPanelIndex?: number | null; // NEU — 0-basiert, Lese-Position
|
||
}
|
||
```
|
||
|
||
Das `comicPanelIndex`-Feld ist redundant mit `story.panelImageIds`, aber
|
||
erlaubt der Picture-Galerie-Ansicht, direkt "Panel 3 von Story X"
|
||
anzuzeigen ohne die Story zu laden. Plaintext-Zahl, kein
|
||
Registry-Change.
|
||
|
||
### `verifyMediaOwnership` erweitert
|
||
|
||
`apps/api/src/modules/picture/routes.ts:299-318` — die erlaubten Apps
|
||
um `'comic'` erweitern, damit Wardrobe-Garments als Kostüm-Referenz in
|
||
Comic-Panel-Generierungen verwendet werden können:
|
||
|
||
```typescript
|
||
verifyMediaOwnership(userId, refIds, ['me', 'wardrobe', 'comic'])
|
||
```
|
||
|
||
(`'comic'` für zukünftige comic-eigene Referenz-Uploads wie
|
||
Panel-Anker-Bilder in M6+; aktuell leer, aber der Slot ist reserviert.)
|
||
|
||
## Modul-Struktur
|
||
|
||
```
|
||
apps/mana/apps/web/src/lib/modules/comic/
|
||
├── types.ts # ComicStyle, LocalComicStory, ComicPanelMeta
|
||
├── collections.ts # comicStoriesTable
|
||
├── queries.ts # useAllStories, useStoryById, useStoriesByInput
|
||
├── module.config.ts # { appId: 'comic', tables: ['comicStories'] }
|
||
├── styles.ts # STYLE_PREFIXES: Record<ComicStyle, string>
|
||
├── stores/
|
||
│ └── stories.svelte.ts # createStory, updateStory, appendPanel,
|
||
│ # reorderPanels, removePanel, updatePanelMeta,
|
||
│ # archive, delete
|
||
├── api/
|
||
│ ├── generate-panel.ts # runPanelGenerate({story, prompt, caption, dialogue})
|
||
│ │ # — wraps /picture/generate-with-reference
|
||
│ └── storyboard.ts # (M4) suggestPanels({style, sourceText, panelCount})
|
||
│ # — client-side Claude-Call via @mana/shared-ai
|
||
├── components/
|
||
│ ├── StoryCard.svelte # Grid tile (Cover = panelImageIds[0])
|
||
│ ├── StoryForm.svelte # Create/edit Sheet (title, style, character, context)
|
||
│ ├── StylePicker.svelte # 5 Presets als radio-tiles
|
||
│ ├── CharacterPicker.svelte # meImages face-ref auto-select + optional garments
|
||
│ ├── PanelStrip.svelte # horizontal scroll, panel thumbnails
|
||
│ ├── PanelCard.svelte # einzelnes Panel mit Caption/Dialog-Anzeige
|
||
│ ├── PanelEditor.svelte # Prompt + Caption + Dialog + "Generieren"-Button
|
||
│ ├── StoryboardSuggester.svelte # (M4) Input-Picker + Claude-Suggestion-Liste
|
||
│ └── ReferenceInputPicker.svelte # (M4) wählt Journal/Notes/Library/Writing/Calendar
|
||
├── views/
|
||
│ ├── ListView.svelte # Grid aller Stories
|
||
│ └── DetailView.svelte # Story-Meta + PanelStrip + "+ Panel" CTA
|
||
├── constants.ts # STYLE_LABELS, MAX_PANELS_PER_STORY (default 12)
|
||
└── index.ts
|
||
```
|
||
|
||
Route-Seiten:
|
||
|
||
```
|
||
apps/mana/apps/web/src/routes/(app)/comic/
|
||
├── +page.svelte # → ListView
|
||
├── [id]/+page.svelte # → DetailView
|
||
└── new/+page.svelte # → StoryForm (create)
|
||
```
|
||
|
||
Kein Composer-Route wie bei Wardrobe — Comic-Erstellung ist kurz
|
||
(Titel + Stil + Character = 3 Felder), Panel-Editing läuft im
|
||
Detail-View als inline-Sheet.
|
||
|
||
## Backend
|
||
|
||
**Neuer App-Slot `'comic'`** für zukünftige Uploads (Panel-Anker,
|
||
Custom-Backgrounds in M6+). In M1 genügt die Registrierung des Slots
|
||
in `verifyMediaOwnership` + der App-Allowlist; eigener Upload-Endpoint
|
||
ist M1 nicht nötig, weil Panel-Bilder als `picture.images` über den
|
||
bestehenden Generate-Flow entstehen.
|
||
|
||
**Keine eigene Generate-Route:** `runPanelGenerate()` ruft direkt
|
||
`/api/v1/picture/generate-with-reference`, analog zu Wardrobe. Nach
|
||
Erfolg schreibt der Client die `comicStoryId` + `comicPanelIndex`-
|
||
Back-Refs auf die `picture.images`-Row *und* appendet die imageId auf
|
||
`story.panelImageIds` + setzt `story.panelMeta[imageId]`.
|
||
|
||
**Cap-Prüfung:** `MAX_REFERENCE_IMAGES=8` (bereits in Wardrobe M1
|
||
gesetzt) deckt Comic ab — Face (1) + Body (1) + bis zu 3 Kostüm-Fotos
|
||
= 5, mit Puffer für M6+ Anchor-Panel.
|
||
|
||
**mana-apps.ts Eintrag:** `packages/shared-branding/src/mana-apps.ts`
|
||
bekommt einen neuen Eintrag:
|
||
|
||
```typescript
|
||
{
|
||
id: 'comic',
|
||
name: 'Comic',
|
||
description: 'Aus Text wird ein Comic',
|
||
icon: 'BookImage' /* oder similar */,
|
||
color: '#…' /* TBD, siehe design-ux.md für Palette */,
|
||
requiredTier: 'beta',
|
||
route: '/comic',
|
||
}
|
||
```
|
||
|
||
## MCP-Tools (`packages/mana-tool-registry/src/modules/comic.ts`)
|
||
|
||
Vier Tools, Pattern 1:1 an `wardrobe.ts` angelehnt:
|
||
|
||
- **`comic.listStories({style?, favoriteOnly?, limit?})`** — read, auto.
|
||
Pullt via mana-sync `app='comic'`, entschlüsselt `title`+`description`+
|
||
`tags`+`panelMeta`. Filter client-side.
|
||
- **`comic.createStory({title, style, characterMediaIds, description?, storyContext?})`** —
|
||
write, propose. Validiert dass alle `characterMediaIds` dem User
|
||
gehören (`app='me'|'wardrobe'`). Schreibt via `pushInsert`.
|
||
- **`comic.generatePanel({storyId, panelPrompt, caption?, dialogue?, sourceInput?})`** —
|
||
write (kostet Credits), propose. Liest die Story, composed den finalen
|
||
Prompt (stylePrefix + panelPrompt + caption/dialog-Hints), ruft
|
||
`/picture/generate-with-reference`, appendet das Ergebnis auf
|
||
`panelImageIds` + `panelMeta`.
|
||
- **`comic.reorderPanels({storyId, panelImageIds})`** — write, propose.
|
||
Validiert Set-Equality (keine neuen/fehlenden IDs), schreibt die neue
|
||
Reihenfolge.
|
||
|
||
`AI_TOOL_CATALOG` in `@mana/shared-ai/src/tools/schemas.ts` bekommt die
|
||
vier Tools, `comic` kommt in die `ModuleId`-Union.
|
||
|
||
## Milestones
|
||
|
||
- **M1 — Datenschicht & Modul-Registrierung**
|
||
- [ ] Dexie v43: `comicStories` mit Indices `[createdAt, style, isFavorite, isArchived]` (space-scoped, kein Compound-Index)
|
||
- [ ] `types.ts`: `ComicStyle`, `LocalComicStory`, `ComicPanelMeta`, `toStory`-Converter
|
||
- [ ] Encryption-Registry-Eintrag für `comicStories` (`title/description/storyContext/tags/panelMeta`)
|
||
- [ ] `collections.ts`, `queries.ts` (useAllStories, useStoryById) via `scopedForModule<>`
|
||
- [ ] `stores/stories.svelte.ts` mit createStory + archive + delete (Panel-Methoden kommen in M2)
|
||
- [ ] `module.config.ts` registriert `appId='comic'`
|
||
- [ ] `comic` in alle sechs Space-Typen der Allowlist (`packages/shared-types/src/spaces.ts`)
|
||
- [ ] `mana-apps.ts` Eintrag mit `requiredTier: 'beta'`
|
||
- [ ] `picture.images.comicStoryId` + `comicPanelIndex` Felder + `toImage`-Converter
|
||
- [ ] `verifyMediaOwnership` um `'comic'` erweitern
|
||
- [ ] Encryption-Roundtrip-Test für `panelMeta`-JSON (wie library M1 für kind-discriminator)
|
||
|
||
- **M2 — Story-CRUD + Single-Panel-Generierung**
|
||
- [ ] Route `/comic` → `ListView`, Story-Grid mit `StoryCard` (Cover = `panelImageIds[0]` → mana-media URL, Fallback Placeholder für Stories ohne Panels)
|
||
- [ ] Route `/comic/new` → `StoryForm` (Title, `StylePicker` mit 5 Presets, `CharacterPicker` bindet an `useImageByPrimary('face-ref')` + optional body-ref-Add + Wardrobe-Garment-Picker für bis zu 3 Kostüme, optional `storyContext`-Textarea)
|
||
- [ ] Route `/comic/[id]` → `DetailView`: Meta-Card + `PanelStrip` (horizontal scroll) + "+ Panel" CTA
|
||
- [ ] `PanelEditor` inline-Sheet: Prompt-Textarea, Caption-Freitext, Dialog-Freitext, "Generieren"-Button
|
||
- [ ] `api/generate-panel.ts`: `runPanelGenerate({story, prompt, caption, dialogue})` composed den Prompt (`styles.ts` liefert stylePrefix) und ruft `/picture/generate-with-reference`
|
||
- [ ] Nach Erfolg: `picture.images.comicStoryId` + `comicPanelIndex` setzen + `story.panelImageIds.push()` + `panelMeta[imageId] = {…}`
|
||
- [ ] Panel-Lösch-Button (Dexie-Row der `picture.images` bleibt — nur aus `panelImageIds` und `panelMeta` entfernen; User kann im Picture-Modul final löschen)
|
||
- [ ] Non-personal-Space-Hinweis + Empty-State bei fehlenden meImages (Link zu `/profile/me-images`)
|
||
- [ ] Visibility-Felder setzbar via `<VisibilityPicker>` in DetailView
|
||
|
||
- **M3 — Batch-Panel-Generierung**
|
||
- [ ] `PanelEditor` unterstützt Multi-Panel-Modus: 2–4 Prompts im Formular, "Alle generieren"-Button
|
||
- [ ] Client startet N parallele `/picture/generate-with-reference`-Calls, zeigt Progress-Bar pro Panel
|
||
- [ ] Credit-Hinweis zeigt Gesamtkosten vorher (`n × creditsForQuality(medium)`)
|
||
- [ ] Retry-UI falls 1 von N fehlschlägt (nur der fehlgeschlagene wird erneut generiert)
|
||
- [ ] `comic.generatePanel` MCP-Tool bekommt optional `count?: 1..4`-Parameter (default 1)
|
||
|
||
- **M4 — AI-Storyboard aus Cross-Modul-Input**
|
||
- [ ] `ReferenceInputPicker`-Komponente: Modul-Tabs (Journal / Notes / Library / Writing / Calendar), pro Tab Live-Query der letzten N Einträge mit Suche
|
||
- [ ] Per ausgewähltem Entry: `<module>Store.getEntry(id)` → decrypt content → in Storyboard-Flow reichen
|
||
- [ ] `api/storyboard.ts`: `suggestPanels({style, sourceText, panelCount=4})` ruft Claude (via `@mana/shared-ai`, client-side, genau wie AI-Workbench-Planer — kein neuer Service-Pfad), erwartet `Panel[]` als strukturierte Antwort `{prompt, caption, dialogue}`
|
||
- [ ] `StoryboardSuggester`-Komponente zeigt Claude-Vorschläge als editierbare Liste (Prompt + Caption + Dialog pro Panel), User kann editieren/löschen/Reihenfolge ändern
|
||
- [ ] "Alle generieren"-Button übergibt die bestätigte Panel-Liste an den M3-Batch-Pfad
|
||
- [ ] `panelMeta[imageId].sourceInput = {module, entryId}` beim Erzeugen gesetzt
|
||
- [ ] `useStoriesByInput({module, entryId})` Query für künftige Cross-Reference-UI ("Comics zu diesem Journal-Eintrag")
|
||
|
||
- **M5 — MCP-Tools + Visibility-Polish**
|
||
- [ ] `packages/mana-tool-registry/src/modules/comic.ts` mit 4 Tools: listStories, createStory, generatePanel, reorderPanels
|
||
- [ ] `'comic'` in `ModuleId`-Union
|
||
- [ ] `registerComicTools()` in `registerAllModules()`
|
||
- [ ] `AI_TOOL_CATALOG` in `@mana/shared-ai/src/tools/schemas.ts` erweitert
|
||
- [ ] Propose-Policy für `createStory`/`generatePanel`/`reorderPanels`, auto-Policy für `listStories`
|
||
- [ ] `<VisibilityPicker>` voll integriert inkl. `unlistedToken`-Generierung, `canEmbedOnWebsite` check für public Comics
|
||
- [ ] Embed-Route `/embed/comic/[id]` (public + unlisted) mit Panel-Strip-Render (wie andere Visibility-adoptierte Module)
|
||
|
||
- **M6 — Persona-Template "Comic-Autor"** (optional, ~0.5 Tag)
|
||
- [ ] Persona-Template: auto-Policy für `comic.listStories` + `journal.list*` + `notes.list*`, propose-Policy für `comic.createStory` + `comic.generatePanel`
|
||
- [ ] Seed-Prompt: "Du bist Comic-Autor. Wenn der User dir einen Moment, ein Erlebnis oder eine Idee gibt, schlag ihm einen kurzen Comic vor — Titel, Stil, 4 Panels mit Prompt + Caption + Dialog. Humor wenn der User es leicht nimmt, ernst wenn er es ernst nimmt."
|
||
|
||
- **M7 — Comic-Strip-Canvas** (optional, mehrere Tage)
|
||
- [ ] Picture-Boards-Pattern adaptieren für Comic: freie Panel-Positionierung, variable Panel-Größen, Gutter, Speech-Bubble-Overlay (dann doch SVG, opt-in pro Story)
|
||
- [ ] Export als einzelnes PNG/PDF-Asset (Panel-Strip → Canvas → Blob)
|
||
- [ ] Rechtfertigt sich nur, wenn Nutzer Feedback-Signal senden dass die lineare Liste nicht reicht
|
||
|
||
- **M8 — Multi-Character-Crew** (optional, mehrere Tage)
|
||
- [ ] Story bekommt `characterCast: CharacterRef[]` statt flaches `characterMediaIds[]`
|
||
- [ ] Pro Panel kann der Autor einen oder mehrere Cast-Member auswählen; `referenceMediaIds` wird pro Panel zusammengesetzt
|
||
- [ ] Namens-Mapping (Cast-Member bekommt Namen → Dialog kann "Alice sagt:" taggen)
|
||
- [ ] Nur starten wenn Single-Character-Flow nach M5-Soak stabil
|
||
|
||
## Verschlüsselung
|
||
|
||
Alle user-typed Felder verschlüsselt (siehe Registry-Einträge oben).
|
||
`panelMeta` als ganzer JSON-Blob verschlüsselt (nicht per-Feld) — einfacher
|
||
Roundtrip, gleiche Semantik wie bei Library's kind-spezifischen
|
||
Metadaten.
|
||
|
||
Bild-Blobs selbst bleiben in mana-media mit Owner-RLS, identisch zu
|
||
Picture/Wardrobe/Me-Images. Zero-Knowledge-Nutzer: MCP-Tools fallen
|
||
stumm aus (kein MK → `ctx.getMasterKey()` throwt), UI-Flow bleibt
|
||
funktional weil die Decrypts client-side passieren.
|
||
|
||
## Cross-Modul-Impact
|
||
|
||
| Modul | Impact |
|
||
|---|---|
|
||
| `picture` | Zwei neue optionale Felder auf `LocalImage`: `comicStoryId`, `comicPanelIndex`. Keine Registry-Änderung (beide plaintext). Galerie-View könnte optional ein "Teil von Comic X"-Chip zeigen (M5+ optional). |
|
||
| `me-images` | Nichts — Comic konsumiert nur `useImageByPrimary`. |
|
||
| `wardrobe` | Nichts — Comic liest Garments als referenzielle `mediaIds`, schreibt nicht zurück. |
|
||
| `journal`, `notes`, `library`, `writing`, `calendar` | Nichts — nur lesende Cross-Module-Reads über die Module-Stores. |
|
||
| `shared-branding` | Neuer App-Eintrag `comic` (Icon, Farbe, Tier=beta). |
|
||
| `shared-types/spaces.ts` | `comic` in alle sechs Space-Typen der Allowlist (`personal`, `brand`, `club`, `family`, `team`, `practice`). |
|
||
| `shared-ai/tools/schemas.ts` | 4 neue Einträge im `AI_TOOL_CATALOG`. |
|
||
| `mana-tool-registry` | Neues Modul `comic.ts` + `registerComicTools()`. |
|
||
| `apps/api/picture/routes.ts` | `verifyMediaOwnership` um `'comic'` erweitern. |
|
||
|
||
## Offene Fragen (vor M1 klären)
|
||
|
||
1. **Panel-Count-Limit pro Story**: 8? 12? 20? → Empfehlung: hartes
|
||
Client-Limit 12 in `constants.ts`, weicher Hinweis ab 8 ("lange Comics
|
||
sind mit gpt-image-2 schwer konsistent zu halten"). Erhöhen nach
|
||
M5-Soak möglich.
|
||
2. **Quality-Default für Panels**: `medium` (10 Credits)? → Ja, wie
|
||
Wardrobe. User kann pro Panel overriden (low/medium/high); Batch-Modus
|
||
nutzt eine Story-weite Default-Setting.
|
||
3. **Stil-Wechsel nachträglich**: erlaubt? → Nein, Stil ist fix nach
|
||
Story-Create. Wer wechseln will, dupliziert die Story (M6+ Feature)
|
||
oder erstellt neu.
|
||
4. **Dialog/Caption Sprache**: User-Sprache oder Englisch? → Default
|
||
User-Sprache (Deutsch in unserem primären Markt). UI-Hinweis dass
|
||
Englisch stabiler rendert. Kein Auto-Translate in M1–M5.
|
||
5. **AI-Storyboard-Panel-Count**: Claude schlägt 4–6 Panels vor, der
|
||
User kann mehr/weniger anfordern? → Default 4, Slider 2–8 im UI, Hard-Cap 8.
|
||
6. **Panel-Lösch-Semantik**: beim Entfernen aus `panelImageIds` auch die
|
||
`picture.images`-Row löschen? → Nein. Row bleibt, nur die
|
||
Story-Referenz geht weg. User kann das Panel in der Picture-Galerie
|
||
behalten oder dort final löschen. Symmetrisch zu Wardrobe (Try-On-
|
||
Bilder überleben eine Outfit-Löschung).
|
||
|
||
## §11 Character-System (Mc1–Mc5)
|
||
|
||
Nachgezogen 2026-04-25, weil sich im Soak gezeigt hat: rohe meImages
|
||
direkt als Story-Refs sind kein guter „Identity-Anchor". gpt-image-2
|
||
und Nano Banana variieren zwischen Calls — Panel 1 sieht anders aus
|
||
als Panel 4. User hat zwischen den Panels keine Iteration, kein
|
||
„nochmal probieren bis das Aussehen stimmt".
|
||
|
||
Lösung: ein **Comic-Character** als eigene Entität, die der Nutzer
|
||
einmal aufbaut + iteriert + pinnt, und die dann als stabiler
|
||
Story-Anchor dient.
|
||
|
||
### Datenmodell
|
||
|
||
Eigenes Table `comicCharacters` (Sibling zu `comicStories`,
|
||
**space-scoped** wie comicStories — Source-meImages sind ja auch
|
||
space-scoped post-v40, sonst orphan-Refs nach Space-Wechsel).
|
||
|
||
```typescript
|
||
interface LocalComicCharacter extends BaseRecord {
|
||
id: string;
|
||
name: string; // "Manga-Me", "Cartoon-Casual"
|
||
description?: string | null;
|
||
style: ComicStyle; // mit welchem Stil generiert
|
||
addPrompt?: string | null; // user-typed Add-Prompt zum Stil
|
||
|
||
sourceFaceMediaId: string; // welche meImages dienten als Source
|
||
sourceBodyMediaId?: string | null;
|
||
|
||
variantMediaIds: string[]; // alle generierten Versuche (FK auf picture.images)
|
||
pinnedVariantId?: string | null; // welcher Versuch IST der Charakter
|
||
|
||
tags: string[];
|
||
isFavorite?: boolean;
|
||
isArchived?: boolean;
|
||
}
|
||
```
|
||
|
||
**Encryption**: name / description / addPrompt / tags. Style + IDs
|
||
+ Variant-Liste + Booleans bleiben plaintext.
|
||
|
||
`picture.images` bekommt einen `comicCharacterId`-Back-Ref (analog
|
||
zu `comicStoryId`/`wardrobeOutfitId`/`wardrobeGarmentId`). Mutually
|
||
exclusive mit `comicStoryId` — eine Image-Row ist entweder Panel
|
||
ODER Variant, nie beides.
|
||
|
||
### Snapshot-Semantik
|
||
|
||
Stories speichern **mediaId at create time**, nicht den
|
||
`characterId` als Live-Lookup. Re-Pinning eines Characters ändert
|
||
also keine bestehenden Stories — die haben den alten Variant
|
||
weiter als Ref. Neue Stories nach dem Re-Pin nutzen den neuen.
|
||
|
||
### UX-Flow
|
||
|
||
**Mc1 — Datenschicht** (3h): Dexie v49 + types + crypto-registry +
|
||
collections + queries (`useAllCharacters`, `useCharacter`,
|
||
`useCharactersByStyle`) + Store (`createCharacter`, `appendVariant`,
|
||
`pinVariant`, `removeVariant`, `updateCharacter`, `archive`, `delete`).
|
||
`picture.images.comicCharacterId` + Module-Registry-Tabellenliste +
|
||
Encryption-Roundtrip-Test.
|
||
|
||
**Mc2 — UI** (5h):
|
||
- Routes `/comic/character`, `/comic/character/new`,
|
||
`/comic/character/[id]`
|
||
- ListView-Root bekommt 2-Tab-UI: **Stories | Characters**
|
||
- `CharacterBuilder.svelte`: Source picken (face Pflicht, body
|
||
optional), Stil picken, Add-Prompt optional, „Generieren"-Button
|
||
feuert 4 parallele Variant-Calls (n=4 in einem gpt-image-2-Call).
|
||
Variant-Grid darunter, User pinnt eine, „Mehr Varianten" appendet
|
||
weitere 4.
|
||
- `CharacterCard.svelte`: Cover = pinned-variant (oder erste
|
||
Variant als Fallback), Style-Badge, Favorit-Heart.
|
||
- `api/generate-character.ts`: `runCharacterGenerate({character,
|
||
n=4})` ruft `/picture/generate-with-reference` mit
|
||
`[face, body?]`-Refs + Stil-Prefix + Add-Prompt, schreibt N
|
||
picture.images mit `comicCharacterId`-Back-Ref, ruft
|
||
`appendVariant` für jeden.
|
||
|
||
**Mc3 — Story-Create-Update** (3h):
|
||
- StoryForm wechselt von „face/body/garments-Picker" auf
|
||
`CharacterRefPicker.svelte`:
|
||
- Default-Modus: Grid existierender Characters (gefiltert
|
||
nach Stil oder „Alle"). Pick = einzige Story-Character-Ref.
|
||
- „+ Neuer Character" navigiert zu `/comic/character/new` mit
|
||
Return-URL.
|
||
- Toggle „Quick-Modus (kein Character)": fällt zurück auf
|
||
altes Pattern (face + body + garments) — für „mal eben
|
||
schnell aus dem Tagebuch ohne Setup".
|
||
- Story-Type bekommt:
|
||
- `characterId?: string` (FK auf comicCharacters, für
|
||
Anzeige + Click-Through; null im Quick-Modus)
|
||
- `characterMediaId?: string` (Snapshot der gepinnten
|
||
Variant zum Story-Create-Zeitpunkt — was der Renderer
|
||
nutzt)
|
||
- **Soft-Migration**: bestehende Stories mit `characterMediaIds[]`
|
||
bleiben kompatibel; runPanelGenerate prüft erst
|
||
`characterMediaId` (Snapshot), dann fällt zurück auf
|
||
`characterMediaIds[0..n]`. Hard-Migration in einem Folge-Commit
|
||
wenn alle Stories migrert sind.
|
||
- Optional `costumeGarmentIds: string[]` für Wardrobe-Refs
|
||
zusätzlich zum Character (Kostüm über dem Character).
|
||
|
||
**Mc4 — MCP + AI-Catalog** (~2h, optional):
|
||
- `comic.listCharacters`, `comic.createCharacter`,
|
||
`comic.generateVariant`, `comic.pinVariant` in
|
||
packages/mana-tool-registry.
|
||
- `list_comic_characters`, `create_comic_character`,
|
||
`generate_character_variant` in AI_TOOL_CATALOG.
|
||
- Persona kann „mach mir einen Manga-Character für Story X" sagen.
|
||
|
||
**Mc5 — Wardrobe-Hook** ✅ shipped:
|
||
- In Wardrobe-DetailOutfitView ein „Als Comic-Character"-Knopf
|
||
unterhalb des TryOnButton, navigiert zu
|
||
`/comic/character/new?title=…&prompt=wearing+the+OUTFITNAME+outfit`.
|
||
- In DetailGarmentView analog mit `prompt=wearing+GARMENTNAME`.
|
||
- CharacterBuilder akzeptiert `initialName` / `initialAddPrompt` /
|
||
`initialStyle`-Props. Die `/comic/character/new`-Route liest
|
||
URL-Params und reicht sie als initial state durch — der Builder
|
||
startet mit dem prefillten Add-Prompt, User picked Stil + rendert
|
||
die ersten 4 Varianten selbst.
|
||
- Bewusst KEIN Try-On-Output als sourceBodyMediaId: das
|
||
Try-On-Bild ist mit `app='picture'` getaggt, der
|
||
`verifyMediaOwnership`-Check des Comic-Endpoints akzeptiert nur
|
||
`['me', 'wardrobe', 'comic']`. Re-Upload als 'comic' wäre eine
|
||
zusätzliche Server-Route — Aufwand vs. Nutzen nicht klar.
|
||
Workflow stattdessen: rohe meImages bleiben Source, der
|
||
Add-Prompt steuert den Outfit-Look.
|
||
|
||
### Tradeoffs
|
||
|
||
- **Variant-Count fix bei 4** statt Slider 1-4: 4 ist sweet-spot
|
||
für Auswahl ohne Decision-Fatigue, in einem API-Call ausführbar,
|
||
Credits ~10c × 4 = 40c pro Generate-Round (medium-Quality).
|
||
- **Quick-Modus behalten**: nicht jede Story braucht Setup. Soft
|
||
defaults: existieren Characters → Default-Modus „Pick", sonst
|
||
Default „Quick".
|
||
- **Snapshot statt Live-Ref**: Stories sind stabil. Trade-off:
|
||
re-pinned Characters reflektieren nicht in alten Stories — User
|
||
muss explizit „Story-Charakter aktualisieren"-Flow nutzen
|
||
(M5+ Feature).
|
||
- **Space-scoped Characters**: bewusst nicht user-global, weil
|
||
Source-meImages space-scoped sind. Trade-off: man muss in jedem
|
||
Space einen eigenen Manga-Me bauen. Akzeptabel weil Spaces
|
||
unterschiedliche Settings sind (personal vs. brand).
|
||
|
||
## Verweise
|
||
|
||
- Fundament Picture-Generate-Reference: `apps/api/src/modules/picture/routes.ts:250-430`
|
||
- Wardrobe als Modul-Blaupause: `docs/plans/wardrobe-module.md`
|
||
- Library als Single-Table-Modul mit Discriminator-Pattern: `docs/plans/library-module.md`
|
||
- Writing-Plan für Cross-Modul-Input-Pattern: `docs/plans/writing-module.md`
|
||
- Visibility-System: `docs/plans/visibility-system.md`, `packages/shared-privacy/`
|
||
- Spaces-Modul-Allowlist: `packages/shared-types/src/spaces.ts`
|
||
- Tool-Registry-Pattern: `packages/mana-tool-registry/src/modules/wardrobe.ts`
|
||
- Me-Images (Face/Body-Ref-Konzept): `docs/plans/me-images-and-reference-generation.md`
|