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