From 3d30e39ae76bbc1e38c9280d79b20c27a831e94a Mon Sep 17 00:00:00 2001 From: Till JS Date: Sun, 26 Apr 2026 19:32:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(comic):=20Mc5=20=E2=80=94=20Wardrobe-Hook?= =?UTF-8?q?=20"Als=20Comic-Character"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../comic/components/CharacterBuilder.svelte | 19 +++++++++++----- .../wardrobe/views/DetailGarmentView.svelte | 16 +++++++++++++- .../wardrobe/views/DetailOutfitView.svelte | 16 +++++++++++++- .../(app)/comic/character/new/+page.svelte | 20 ++++++++++++++++- docs/plans/comic-module.md | 22 ++++++++++++++----- 5 files changed, 80 insertions(+), 13 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte b/apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte index 99c84fff6..1e6f8eeae 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte +++ b/apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte @@ -31,27 +31,36 @@ * character — name+style+source are locked, only Add-Prompt * is editable per generation. */ existing?: ComicCharacter; + /** Optional pre-fills for create-mode — used by the wardrobe- + * hook (Mc5) to seed an addPrompt like "wearing the + * Bühnenoutfit" when the user clicks "Als Comic-Character" + * on a Wardrobe-Outfit. Ignored in extend-mode. */ + initialName?: string; + initialAddPrompt?: string; + initialStyle?: ComicStyle; /** Called after the first successful variant batch with the * resulting character id, so the parent route can navigate. */ onCreated?: (characterId: string) => void; onClose?: () => void; } - let { existing, onClose, onCreated }: Props = $props(); + let { existing, initialName, initialAddPrompt, initialStyle, onClose, onCreated }: Props = + $props(); const isExtend = $derived(Boolean(existing)); // Builder state. In extend-mode all of these come from `existing` // at mount time and aren't editable; in create-mode the user fills - // them in. Init-time read of `existing` is intentional — the + // them in (with optional pre-fills from URL-params via the route + // page wrapper). Init-time read is intentional — the // character is always remounted via {#key} when the route id // changes, so capturing the snapshot here is correct. // svelte-ignore state_referenced_locally - let name = $state(existing?.name ?? ''); + let name = $state(existing?.name ?? initialName ?? ''); // svelte-ignore state_referenced_locally - let style = $state(existing?.style ?? 'comic'); + let style = $state(existing?.style ?? initialStyle ?? 'comic'); // svelte-ignore state_referenced_locally - let addPrompt = $state(existing?.addPrompt ?? ''); + let addPrompt = $state(existing?.addPrompt ?? initialAddPrompt ?? ''); type Quality = 'low' | 'medium' | 'high'; const QUALITIES: readonly Quality[] = ['low', 'medium', 'high'] as const; diff --git a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte index 5a739e622..931ff93da 100644 --- a/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte +++ b/apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte @@ -6,7 +6,7 @@ --> @@ -16,6 +34,6 @@ Detail kannst du jederzeit weitere generieren.

- + diff --git a/docs/plans/comic-module.md b/docs/plans/comic-module.md index 40d62f931..fd61cac43 100644 --- a/docs/plans/comic-module.md +++ b/docs/plans/comic-module.md @@ -685,11 +685,23 @@ Encryption-Roundtrip-Test. `generate_character_variant` in AI_TOOL_CATALOG. - Persona kann „mach mir einen Manga-Character für Story X" sagen. -**Mc5 — Wardrobe-Hook** (~2h, optional): -- In Wardrobe-DetailOutfitView nach erfolgreichem Try-On ein - Knopf „Als Comic-Character speichern" → öffnet Builder mit - Try-On-Result als optionalem `sourceBodyMediaId`. -- In DetailGarmentView analog für ein einzelnes Kleidungsstück. +**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