Commit graph

4 commits

Author SHA1 Message Date
Till JS
3d30e39ae7 feat(comic): Mc5 — Wardrobe-Hook "Als Comic-Character"
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>
2026-04-26 19:32:29 +02:00
Till JS
313809bc95 feat(comic): Mc1 — Character-Datenschicht (Iteration + Pinning)
Comic-Modul nutzte bisher rohe meImages direkt als Story-Refs:
gpt-image-2 / Nano Banana variieren zwischen Calls, Panel 1 sah
anders aus als Panel 4, User hatte keine Iteration vor der Story.
Lösung: Comic-Character als eigene Entität, einmal aufgebaut +
iteriert + gepinnt, danach Story-Anchor.

Datenschicht:
- Dexie v49 `comicCharacters` (space-scoped, indices createdAt /
  style / isFavorite / isArchived).
- types.ts: LocalComicCharacter mit name + style + addPrompt +
  sourceFaceMediaId + sourceBodyMediaId? + variantMediaIds[] +
  pinnedVariantId?, plus toCharacter + characterCoverVariantId
  helper (pinned > erste Variant > null).
- crypto/registry.ts: comicCharacters entry — name + description
  + addPrompt + tags encrypted; style + IDs + Variant-Liste +
  Booleans plaintext.
- collections.ts: comicCharactersTable.
- queries.ts: useAllCharacters, useCharactersByStyle, useCharacter
  via scopedForModule (alle space-scoped).
- stores/characters.svelte.ts: createCharacter (auto-pin first
  variant fallback), appendVariant (auto-pin if none yet),
  pinVariant, removeVariant (mit pin-fallback auf erste
  remaining), updateCharacter, toggleFavorite, archiveCharacter,
  deleteCharacter. Arrays werden via [...arr] entproxiet (Svelte
  5 $state defense).
- module.config.ts: comicCharacters in tables-Liste.
- picture/types.ts + queries.ts: comicCharacterId Back-Ref auf
  LocalImage + Image, mutually exclusive mit comicStoryId.
- 3 neue Encryption-Roundtrip-Tests (insgesamt 8 grün) für
  charakter-Row, Build-in-progress (no variants), Roundtrip.

Architektur-Entscheidungen (Plan-Doc §11 dokumentiert):
- **space-scoped**, nicht user-global: Source-meImages sind ja
  selbst space-scoped post-v40, sonst orphan-Refs nach
  Space-Wechsel.
- **Snapshot at story-create**, kein Live-Lookup: Stories
  speichern die mediaId der gepinnten Variant zum Erstellungs-
  zeitpunkt → re-pinning eines Characters lässt bestehende
  Stories unverändert.
- **n=4 fixes Variant-Count**: in einem gpt-image-2-Call
  parallel; sweet-spot für Auswahl ohne Decision-Fatigue.
- **Mutually-exclusive Back-Refs** auf picture.images:
  comicStoryId XOR comicCharacterId — Image ist Panel ODER
  Variant, nie beides.

Mc2 (UI: Builder + Variant-Grid + Routes), Mc3 (Story-Create-
Update + Soft-Migration), Mc4 (MCP/Catalog), Mc5 (Wardrobe-Hook)
folgen separat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:52:58 +02:00
Till JS
364522db87 feat(comic): image-model picker — OpenAI + Nano Banana wählbar
Comic nutzte bisher 'openai/gpt-image-2' hartcodiert auf drei Ebenen
(generate-panel.ts, comic.generatePanel MCP-Tool, generate_comic_panel
AI-Tool). Wardrobe hat seit dem Nano-Banana-Commit einen
TryOnModelPicker mit drei Optionen — Comic spiegelt das jetzt 1:1.

Wählbar in allen drei Editoren (PanelEditor, BatchPanelEditor,
StoryboardSuggester):
- openai/gpt-image-2 (Default) — OpenAI GPT-image Standard
- google/gemini-3-pro-image-preview — Nano Banana Pro, hohe
  Konsistenz, teurer
- google/gemini-3.1-flash-image-preview — Nano Banana 2, neuestes,
  schnell, günstig

Implementierung:
- api/generate-panel.ts: PanelModel Union + DEFAULT_PANEL_MODEL +
  model? Param auf RunPanelGenerateParams + im HTTP-Body
  weitergereicht (vorher hart 'openai/gpt-image-2').
- components/PanelModelPicker.svelte: neue Komponente, Stil/Markup
  identisch zu TryOnModelPicker für Muskel-Memory über beide Flows.
- components/PanelEditor.svelte: `let model = $state(DEFAULT_PANEL_MODEL)`
  + Picker oberhalb der Qualität-/Format-Leiste + model im
  runPanelGenerate-Call.
- components/BatchPanelEditor.svelte: gleiche Änderung — ein Model
  pro Batch (nicht pro Row) damit der Batch konsistent rendert.
- components/StoryboardSuggester.svelte: gleiches Pattern; der
  Picker landet zwischen "Panel manuell"-Button und dem
  Qualität/Format-Block.
- packages/mana-tool-registry/src/modules/comic.ts: generatePanel
  Input-Schema bekommt model mit zod.enum() + default; im Body
  wird input.model durchgereicht.
- packages/shared-ai/src/tools/schemas.ts: generate_comic_panel
  bekommt Parameter 'model' optional mit gleicher Enum-Liste.
- apps/mana/apps/web/src/lib/modules/comic/tools.ts: isValidModel
  Guard + Parameter-Validierung; model an runPanelGenerate.

Keine Story-Level-Persistierung — model bleibt lokaler State pro
Editor-Mount. Eine model-Spalte auf comicStories würde Migration
brauchen und die Wahl ist eh ad-hoc pro Panel/Batch.

Plan-Doc (§2.1) dokumentiert die Entscheidung + die drei Optionen.

107 shared-ai tests weiter grün. check + validate:all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:19:40 +02:00
Till JS
27c1860f82 feat(comic): M1 — Datenschicht + Modul-Registrierung
Neues Comic-Modul: aus Text-Inputs (Journal / Notes / Writing / Library
/ Calendar) entsteht ein mehrseitiger Comic, generiert mit gpt-image-2
über die bestehende /picture/generate-with-reference-Route. Plan in
docs/plans/comic-module.md (M1–M5 + optional M6–M8).

M1 schafft die Datenschicht ohne UI:
- Dexie v44 `comicStories` (space-scoped, Indices createdAt/style/
  isFavorite/isArchived). Story hält `panelImageIds: string[]` und
  `panelMeta: Record<panelImageId, {caption, dialogue, promptUsed,
  sourceInput?}>` — Panels selbst sind picture.images-Rows mit
  comicStoryId + comicPanelIndex Back-Refs.
- Fünf Stil-Presets (comic / manga / cartoon / graphic-novel / webtoon)
  mit Prompt-Prefix-Templates in styles.ts; composePanelPrompt webt
  Stil + Panel-Prompt + Caption + Dialog zusammen. Sprechblasen
  werden von gpt-image-2 direkt ins Bild gerendert — kein SVG-Overlay.
- Encryption-Registry-Eintrag: title / description / storyContext /
  tags / panelMeta als JSON-Blob. Struktur (id, style, character-
  MediaIds, panelImageIds, Flags, visibility) bleibt plaintext.
- Module-Registry registriert appId='comic', verifyMediaOwnership auf
  der /picture/generate-with-reference-Route akzeptiert jetzt
  ['me', 'wardrobe', 'comic'] — 'comic'-Slot ist reserviert für M6+
  Anchor-/Backdrop-Uploads.
- Space-Allowlist: comic in brand (Marken-Storys), club (Vereins-
  geschichte), family (Kinder-Abenteuer), team (Release-Comics),
  practice (Patienten-Aufklärung). Personal via '*'-Sentinel.
- mana-apps.ts Eintrag mit comic-Icon (Sprechblase + Lightning-Bolt,
  f97316→dc2626 Gradient). Lokal tier='guest' mit LOCAL TIER PATCH-
  Comment wie Wardrobe, canonical ist 'beta'.

Visibility-System von Anfang an adopted (setVisibility-Methode im
Store, unlistedToken-Generierung inklusive). appendPanel() als
Vorarbeit für M2 bereits da, ohne Aufrufer.

5 Encryption-Roundtrip-Tests grün (panelMeta nested JSON, leeres
panelMeta, partielle panelMeta ohne sourceInput, null-description).
pnpm run check + validate:all sauber (207 Dexie-Tabellen klassifiziert,
comicStories unter den 106 encrypted).

Kein UI, keine Panel-Generierung, keine MCP-Tools — alles M2/M3/M5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:29:51 +02:00