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>
This commit is contained in:
Till JS 2026-04-24 15:29:51 +02:00
parent 1c82a374fe
commit 27c1860f82
19 changed files with 1385 additions and 5 deletions

View file

@ -75,6 +75,11 @@ const calcSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="n
// Context icon (document/knowledge with sky blue gradient)
const contextSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#contextGrad)"/><rect x="300" y="240" width="424" height="544" rx="24" fill="white"/><path d="M400 400H624" stroke="#0ea5e9" stroke-width="24" stroke-linecap="round"/><path d="M400 480H580" stroke="#0ea5e9" stroke-width="24" stroke-linecap="round" stroke-opacity="0.6"/><path d="M400 560H540" stroke="#0ea5e9" stroke-width="24" stroke-linecap="round" stroke-opacity="0.4"/><path d="M400 640H600" stroke="#0ea5e9" stroke-width="24" stroke-linecap="round" stroke-opacity="0.3"/><path d="M620 240V380H760" stroke="white" stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/><path d="M620 240L760 380" stroke="#0ea5e9" stroke-width="16" stroke-linecap="round" stroke-opacity="0.3"/><circle cx="680" cy="620" r="100" fill="#0ea5e9" fill-opacity="0.2" stroke="white" stroke-width="16"/><path d="M660 620L680 640L720 600" stroke="white" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/><defs><linearGradient id="contextGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#0ea5e9"/><stop offset="1" stop-color="#0284c7"/></linearGradient></defs></svg>`;
// Comic icon — speech bubble with a lightning-bolt panel marker on
// orange→red gradient. Sits warm between Picture (green) and Wardrobe
// (rose) so the Mana launcher reads as a coherent creative family.
const comicSvg = `<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="130" y="130" width="764" height="764" rx="382" fill="url(#comicGrad)"/><path d="M260 340c0-33 27-60 60-60h384c33 0 60 27 60 60v288c0 33-27 60-60 60H480l-108 90v-90h-52c-33 0-60-27-60-60V340z" fill="white"/><path d="M540 370l-90 156h72l-30 128 108-172h-78l28-112h-10z" fill="#ea580c"/><circle cx="360" cy="460" r="18" fill="#ea580c" fill-opacity="0.35"/><circle cx="410" cy="460" r="18" fill="#ea580c" fill-opacity="0.35"/><circle cx="460" cy="460" r="18" fill="#ea580c" fill-opacity="0.35"/><defs><linearGradient id="comicGrad" x1="130" y1="130" x2="894" y2="894" gradientUnits="userSpaceOnUse"><stop stop-color="#f97316"/><stop offset="1" stop-color="#dc2626"/></linearGradient></defs></svg>`;
// Wardrobe icon — T-shirt on hanger with rose-violet gradient.
// Rose/violet to sit between Picture (green) and Calc (pink) without
// clashing; the hanger loop sits on the shoulder line so the silhouette
@ -109,6 +114,7 @@ export const APP_ICONS = {
mail: svgToDataUrl(mailSvg),
inventory: svgToDataUrl(inventorySvg),
wardrobe: svgToDataUrl(wardrobeSvg),
comic: svgToDataUrl(comicSvg),
questions: svgToDataUrl(questionsSvg),
context: svgToDataUrl(contextSvg),
citycorners: svgToDataUrl(citycornersSvg),

View file

@ -394,6 +394,23 @@ export const MANA_APPS: ManaApp[] = [
status: 'beta',
requiredTier: 'guest', // LOCAL TIER PATCH — revert to 'beta' before release
},
{
id: 'comic',
name: 'Comic',
description: {
de: 'Aus Text wird ein Comic',
en: 'Turn text into comics',
},
longDescription: {
de: 'Erstelle mehrseitige Comics mit KI. Starte mit einem Tagebuch-Eintrag, einer Notiz oder einem Kalender-Event und generiere Panels in fünf Stilen — Comic, Manga, Cartoon, Graphic Novel oder Webtoon. Du selbst bist der Protagonist.',
en: 'Create multi-panel comics with AI. Start from a journal entry, note, or calendar event and generate panels in five styles — comic, manga, cartoon, graphic novel, or webtoon. You are the protagonist.',
},
icon: APP_ICONS.comic,
color: '#f97316',
comingSoon: false,
status: 'beta',
requiredTier: 'guest', // LOCAL TIER PATCH — revert to 'beta' before release
},
{
id: 'questions',
name: 'Questions',

View file

@ -89,6 +89,7 @@ export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[]
'activity',
'goals',
'wardrobe', // Merch-Katalog (T-Shirts, Caps, Zip-Hoodies)
'comic', // Marken-Comics / Launch-Stories / Produkt-Storys
],
club: [
@ -117,6 +118,7 @@ export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[]
'activity',
'goals',
'wardrobe', // Vereinstrikots, Club-Bekleidung
'comic', // Vereinsgeschichte, Event-Rückblicke als Comic
],
family: [
@ -143,6 +145,7 @@ export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[]
'firsts',
'wardrobe', // Familien-Kleiderschrank (Kinder inkl.); Try-On
// rendert auf dem aufrufenden Elternteil, nicht auf Kindern
'comic', // Familien-Erinnerungen / Kinder-Abenteuer als Comic
],
team: [
@ -169,6 +172,7 @@ export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[]
'activity',
'goals',
'wardrobe', // Bühnen-Kostüme, Uniformen, Produktions-Wardrobe
'comic', // Team-Anekdoten, Retro-Storytelling, Release-Comics
],
practice: [
@ -191,6 +195,7 @@ export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[]
'activity',
'goals',
'wardrobe', // Praxis-Kittel, Dresscode-Items
'comic', // Patienten-Aufklärungs-Comics, Praxis-Storys
],
} as const;