feat(writing): M1+M2 — new Ghostwriter module with manual draft CRUD

M1 (skeleton):
- Module `writing` registered: 4 Dexie tables (writingDrafts,
  writingDraftVersions, writingGenerations, writingStyles) in v43,
  encrypted via typed registry entries, space-scoped via the Dexie hook.
- App entry in mana-apps.ts (sky-cyan #0ea5e9, LOCAL TIER PATCH guest),
  fountain-pen icon in app-icons.ts.
- Plan: docs/plans/writing-module.md — 12 milestones, Ghostwriter-first
  with Canvas deferred to M9, Picture-pattern analogue (Draft + Version
  + Generation), 9 preset styles, Space-Kontext-as-default.

M2 (manual CRUD):
- drafts store: createDraft (atomic draft + initial v1), updateBriefing,
  setStatus, toggleFavorite, deleteDraft (cascade soft-delete versions),
  updateVersionContent (live edit), createCheckpointVersion,
  restoreVersion (pointer flip, non-destructive), setVisibility.
- styles store: createStyle, updateStyle, upsertExtractedPrinciples,
  setSpaceDefault (exclusive flip), deleteStyle.
- queries: useAllDrafts, useDraft, useVersionsForDraft,
  useCurrentVersionForDraft (follows the pointer so restoreVersion shows
  up in the editor), useGenerationsForDraft, useAllStyles + helpers.
- UI: KindTabs (shows only kinds with drafts), StatusBadge, StatusFilter,
  DraftCard (<button> for a11y), BriefingForm (topic/kind/audience/tone/
  length/language/extra), VersionEditor (500ms debounce + onBlur flush),
  VersionHistory (restore button per version).
- Routes: /writing list + /writing/draft/[id] with {#key id} remounting.

User flow: create draft from briefing → land in detail view → type →
autosave → "Als Checkpoint speichern" for a new version → restore any
older version from the history panel. No AI yet; M3 wires mana-llm for
short-form generation and M7 switches to mana-ai missions for long-form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-24 14:59:56 +02:00
parent 259f6fb316
commit 3c3b2ebbc7
27 changed files with 3484 additions and 0 deletions

View file

@ -0,0 +1,458 @@
# Writing — Module Plan
## Status (2026-04-24)
**Planung.** Noch nichts geshipped. Nächster Schritt: M1 (Skelett).
## Ziel
Ein Modul, mit dem der Nutzer dem AI-Agenten Brief + Stil + Referenzen gibt und **fertige Texte** produziert: Blogposts, Essays, Mails, Bewerbungen, Social Posts, Reden, Storys, Produkttexte. Kernfrage: *"Ich brauche einen Text zu X im Stil Y — schreib ihn."*
**Start-Modus: Ghostwriter.** Input → fertiger Entwurf. Nutzer bewertet ganze Versionen, verfeinert Stellen gezielt mit Selection-Tools. Ein späterer **Canvas-Modus** (freies Tippen, Inline-Autocomplete, `/`-Kommandos) ist als M9 eingeplant, aber nicht Teil des Kern-Scopes.
Nicht im Scope Phase 1:
- Freies Notizen/Journalen (→ `notes` / `journal`)
- Speichern externer Artikel (→ `articles`)
- Kollaboratives Echtzeit-Editing
- Automatische Veröffentlichung (Hand-Off zu `website` / `articles` schon, aber User löst aus)
## Abgrenzung
| Modul | Unterschied |
|---|---|
| `notes` | unstrukturierte Snippets, persönlich, ohne Zweck |
| `journal` | datierte Reflexionen, persönlich |
| `articles` | **konsumierte** Artikel (Readability-Extrakt), Highlights — hier wird gelesen, nicht produziert |
| `chat` | Gespräch, nicht produzierter Text als Artefakt |
| `presi` / `website` | Konsumenten von Text — können aus Writing-Drafts gespeist werden |
| `news-research` / `mana-research` | Recherche-Provider; Writing **konsumiert** diese Quellen als Referenz |
Writing = **intentional produzierter Prosa-Text mit Zweck und Adressat**. Existiert heute nicht.
## Getroffene Entscheidungen (vorab, 2026-04-24)
1. **Ghostwriter-Modus zuerst**, Canvas später.
2. **Styles ≠ Personas**, aber verknüpfbar. Personas (`mana-persona-runner`) bleiben für Agent-Loops; Writing hat eigene `WritingStyle`-Entität. Eine Persona kann einen `defaultWritingStyleId` referenzieren — so nutzt z.B. ein "Marketing-Agent" automatisch den "Corporate Tone"-Style.
3. **Versionierung**: Jede *volle* Generierung/Regeneration → neue `LocalDraftVersion`. Selection-basierte Refinements (Shorten/Expand/Tone) modifizieren die aktuelle Version in-place mit lokalem Undo-Stack, ohne Versions-Explosion. Erst wenn der User "Diese Änderungen übernehmen als neue Version" klickt, wird eine Version geschrieben.
4. **Kind-Liste breit von Anfang an**: `blog`, `essay`, `email`, `social`, `story`, `letter`, `speech`, `cover-letter`, `product-description`, `press-release`, `bio`, `other`. Start mit vollem Set — Templates pro Kind kommen später. Das Discriminator-Feld ist billig; nachträglich einen Kind umzubenennen ist teurer.
5. **Space-Kontext als Default-Stil**: In einem Firmen-/Team-Space wird ein `spaceDefaultStyleId` unterstützt. Der Space kann "Corporate Tone" + standardmäßig verknüpfte kontextDocs als Default-Referenzen setzen. Personal-Space → kein Default, User wählt Style pro Draft.
## Modul-Struktur
```
apps/mana/apps/web/src/lib/modules/writing/
├── types.ts # LocalDraft, Draft, Kind, Status, LocalGeneration, LocalDraftVersion, LocalWritingStyle
├── collections.ts # drafts + draftVersions + generations + writingStyles Tables + Guest-Seed
├── queries.ts # useAllDrafts, useDraftsByKind, useDraft(id), useVersions(draftId), useStyles, useStats
├── stores/
│ ├── drafts.svelte.ts # createDraft, updateBriefing, deleteDraft, setVisibility, publishVersion, restoreVersion
│ ├── generations.svelte.ts # startGeneration, cancelGeneration, applyGenerationAsVersion
│ └── styles.svelte.ts # createStyle, updateStyle, trainStyleFromSamples, deleteStyle
├── components/
│ ├── BriefingForm.svelte # topic, kind, length, tone, audience, language, style-picker, reference-picker
│ ├── DraftCard.svelte # kompakter Listeneintrag (Titel + kind-Badge + Preview + Last-Updated + Visibility-Icon)
│ ├── KindTabs.svelte # Alle | Blog | Essay | E-Mail | Social | Story | Brief | Rede | ...
│ ├── StatusBadge.svelte # entwurf | in-überarbeitung | fertig | veröffentlicht
│ ├── StylePicker.svelte # Preset-Liste + Custom-Styles + "Schreibe wie ich"-Option
│ ├── ReferencePicker.svelte # cross-modul Picker (articles, notes, library, kontext, goals, URLs)
│ ├── VersionHistory.svelte # vertikale Timeline aller Versions, Diff auf Click, Revert-Button
│ ├── DiffView.svelte # seitlicher oder Inline-Diff zwischen zwei Versionen
│ ├── SelectionToolbar.svelte # erscheint bei Text-Markierung: Kürzen / Erweitern / Ton / Umschreiben / Übersetzen
│ ├── GenerationStatus.svelte # Fortschritts-UI während Generation läuft (Streaming-Preview)
│ └── ProposalInbox.svelte # Refine-Vorschläge, die auf User-Approval warten
├── views/
│ ├── ListView.svelte # Modul-Root: KindTabs + Grid of DraftCards + "+ Neuer Draft"-FAB
│ └── DetailView.svelte # Drei-Spalten-Layout (Briefing | Text | Tools)
├── tools.ts # AI-Tools (siehe AI-Integration)
├── constants.ts # KIND_LABELS, TONE_PRESETS, LENGTH_PRESETS, STYLE_PRESETS
├── presets/
│ └── styles.ts # Preset-Styles: Akademisch, LinkedIn, Hemingway, Casual-Blog, Buzzfeed-Listicle, Nachrichten, ...
├── module.config.ts # { appId: 'writing', tables: [{ name: 'drafts' }, { name: 'draftVersions' }, { name: 'generations' }, { name: 'writingStyles' }] }
└── index.ts # Re-Exports
```
## Daten-Schema
### `LocalDraft` (Dexie)
```typescript
export type DraftKind =
| 'blog' | 'essay' | 'email' | 'social' | 'story'
| 'letter' | 'speech' | 'cover-letter'
| 'product-description' | 'press-release' | 'bio' | 'other';
export type DraftStatus = 'draft' | 'refining' | 'complete' | 'published';
export interface LocalDraft extends BaseRecord {
kind: DraftKind; // plaintext — Diskriminator
status: DraftStatus; // plaintext — filterbar
title: string; // encrypted
briefing: { // encrypted — Kern-Eingabe
topic: string;
audience?: string;
tone?: string; // z.B. "sachlich", "humorvoll", "motivierend"
language: string; // ISO-Code, default 'de'
targetLength?: { // optional — default abgeleitet von kind
type: 'words' | 'chars' | 'minutes';
value: number;
};
extraInstructions?: string;
};
styleId?: string | null; // plaintext — FK auf LocalWritingStyle, null = Ad-hoc
styleOverrides?: { // encrypted — Style-Felder, die diesen Draft übersteuern
tone?: string;
styleNotes?: string;
} | null;
references: DraftReference[]; // plaintext IDs + URLs; encrypted Notes
currentVersionId?: string | null; // plaintext — zeigt auf aktive Version
visibility: VisibilityLevel; // plaintext
visibilityChangedAt?: string | null; // plaintext
visibilityChangedBy?: string | null; // plaintext (userId)
unlistedToken?: string | null; // plaintext — minted beim Flip auf 'unlisted'
publishedTo?: DraftPublishTarget[]; // plaintext — ['website:block/abc', 'articles:xyz']
isFavorite: boolean; // plaintext
}
export interface DraftReference {
kind: 'article' | 'note' | 'library' | 'kontext' | 'goal' | 'url' | 'me-image';
targetId?: string; // plaintext, module-lokal
url?: string; // plaintext
note?: string; // encrypted — was der User an dieser Quelle relevant findet
}
export type DraftPublishTarget = {
module: 'website' | 'articles' | 'social-relay' | 'mail' | 'presi';
targetId: string;
publishedAt: string; // ISO
};
```
### `LocalDraftVersion`
```typescript
export interface LocalDraftVersion extends BaseRecord {
draftId: string; // plaintext — FK
versionNumber: number; // plaintext — 1, 2, 3...
content: string; // encrypted — der Text selbst (Markdown)
wordCount: number; // plaintext
generationId?: string | null; // plaintext — falls AI-generiert
isAiGenerated: boolean; // plaintext
parentVersionId?: string | null; // plaintext — für Branching später
summary?: string | null; // encrypted — optional Auto-Summary fürs History-Panel
}
```
Selection-basierte Refinements erzeugen **keine** neue Version; sie mutieren den `content` der aktuellen Version. Ein Undo-Stack bleibt im lokalen State (nicht synced). "Als neue Version speichern" ist ein expliziter Button.
### `LocalGeneration`
```typescript
export type GenerationStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';
export type GenerationKind =
| 'outline' // Outline aus Briefing
| 'draft-from-brief' // Volltext aus Briefing (direkt)
| 'draft-from-outline' // Volltext aus Outline
| 'selection-rewrite' // Mark. Passage umschreiben
| 'selection-shorten' | 'selection-expand'
| 'selection-tone' | 'selection-translate'
| 'full-regenerate';
export interface LocalGeneration extends BaseRecord {
draftId: string; // plaintext
kind: GenerationKind; // plaintext
status: GenerationStatus; // plaintext
prompt: string; // encrypted — finaler zusammengebauter Prompt
provider: 'mana-ai' | 'mana-llm' | 'local-llm'; // plaintext
model?: string | null; // plaintext — z.B. "claude-opus-4-7"
params?: { // plaintext
temperature?: number;
maxTokens?: number;
} | null;
inputSelection?: { start: number; end: number } | null; // plaintext — nur bei selection-*
output?: string | null; // encrypted — was generiert wurde
outputVersionId?: string | null; // plaintext — FK falls als Version gespeichert
startedAt?: string | null; // plaintext
completedAt?: string | null; // plaintext
durationMs?: number | null; // plaintext
tokenUsage?: { input: number; output: number } | null; // plaintext
error?: string | null; // plaintext — User-lesbarer Fehler
missionId?: string | null; // plaintext — FK zu mana-ai mission, falls async
}
```
### `LocalWritingStyle`
```typescript
export type StyleSource = 'preset' | 'custom-description' | 'sample-trained' | 'self-trained';
export interface LocalWritingStyle extends BaseRecord {
name: string; // encrypted
description: string; // encrypted — Style-Beschreibung
source: StyleSource; // plaintext
presetId?: string | null; // plaintext — falls source='preset'
samples?: Array<{ // encrypted
label: string;
text: string;
sourceRef?: string; // z.B. 'journal:id', 'articles:id'
}>;
extractedPrinciples?: { // encrypted — cached Style-Extraktion
toneTraits: string[];
sentenceLengthAvg?: number;
vocabulary?: string[];
examples?: string[];
rawAnalysis?: string; // Freitext-Analyse
extractedAt: string;
} | null;
isSpaceDefault: boolean; // plaintext — für Space-Kontext-Default
isFavorite: boolean; // plaintext
}
```
**Self-Training** (source='self-trained'): Tool sammelt 1020 Snippets aus `journal` + `notes` + `articles` (Highlights) des Users, extrahiert Prinzipien einmalig, cached als `extractedPrinciples`. Explizite User-Aktion — keine Hintergrund-Analyse.
### Encryption-Registry
```typescript
// apps/mana/apps/web/src/lib/data/crypto/registry.ts
drafts: {
fields: ['title', 'briefing', 'styleOverrides', 'references'], // references: wegen .note
version: 1,
},
draftVersions: {
fields: ['content', 'summary'],
version: 1,
},
generations: {
fields: ['prompt', 'output'],
version: 1,
},
writingStyles: {
fields: ['name', 'description', 'samples', 'extractedPrinciples'],
version: 1,
},
```
Alles Nutzer-getippte: encrypted. IDs, Status, Counts, Timestamps, FK-Pointer: plaintext.
## Routing
```
apps/mana/apps/web/src/routes/(app)/writing/
├── +page.svelte # ListView: KindTabs + Grid
├── [kind]/+page.svelte # Deep-Link: /writing/blog, /writing/email ...
├── draft/[id]/+page.svelte # DetailView (drei-spaltig)
├── new/+page.svelte # Kurz-Briefing-Flow (1-Feld → Kind-Vorschlag → Briefing)
└── styles/+page.svelte # Styles-Verwaltung (Preset durchstöbern, eigene anlegen/trainieren)
```
## UI-Konzept
### ListView (`/writing`)
- **Top**: `KindTabs` (Alle | Blog | Essay | E-Mail | Social | Story | ...)
- **Sekundärleiste**: Status-Chips (Entwurf | In-Überarbeitung | Fertig | Veröffentlicht), Sort (Zuletzt bearbeitet | Titel | Wortzahl), Favoriten-Toggle
- **Grid**: `DraftCard` mit Titel + kind-Badge + 2-Zeilen-Preview (erste Zeilen der aktuellen Version) + Last-Updated + Visibility-Icon + Status-Badge
- **FAB "+"**: öffnet `/writing/new`
### `/writing/new` — Kurz-Briefing-Flow
Drei-Schritt-Wizard in einer Card:
1. "Was möchtest du schreiben?" — ein Textfeld. User tippt z.B. "LinkedIn Post zu meinem neuen Modul".
2. AI schlägt basierend auf Freitext vor: `kind='social'`, Länge=200 Wörter, Ton=professional-excited. User kann adjusten.
3. "Generate" → erstellt Draft, leitet zu DetailView weiter, startet erste Generation.
Alternativ "Ohne Vorschlag anlegen" → leeres Briefing-Form.
### DetailView (`/writing/draft/[id]`)
**Drei Spalten** (responsiv: auf Mobil als Tabs):
**Links — Briefing-Panel** (collapsible):
- `BriefingForm` mit Topic, Kind, Audience, Tone, Language, TargetLength, ExtraInstructions
- `StylePicker` — Preset, Custom, oder "Schreibe wie ich"
- `ReferencePicker` — Cross-Modul-Picker: articles, notes, library, kontext, goals, URLs
- "Generate" / "Regenerate" Button — triggert volle Generation → neue Version
- Visibility-Picker (`<VisibilityPicker>` aus shared-privacy)
**Mitte — Text**:
- Editierbarer Textbereich (Markdown, WYSIWYG-Toggle)
- Bei Selektion: `SelectionToolbar` erscheint → Kürzen / Erweitern / Ton / Umschreiben / Übersetzen
- Top-Bar: aktuelle Version, Wortzahl, Sprache, "Als neue Version speichern"-Button
- Live-Streaming während aktiver Generation (Overlay mit Streaming-Preview)
**Rechts — Tools & Context**:
- `VersionHistory` — Timeline aller Versions, Click → Diff, Revert
- Referenzen-Liste (aus Briefing) mit "Öffnen"-Link
- `ProposalInbox` — wartende Refine-Vorschläge (falls `propose`-Policy)
- "Veröffentlichen als..." → Dropdown: Website, Artikel, E-Mail, PDF-Export, Zwischenablage
### Styles-Verwaltung (`/writing/styles`)
- Grid: Preset-Styles + Custom-Styles
- Button "Eigenen Style trainieren" — öffnet Dialog:
- Option A: Style-Beschreibung eintippen ("akademisch, prägnant, aktiv formuliert")
- Option B: Textproben hochladen/aus bestehenden Drafts/Notes importieren → One-Shot-Extraction
- Option C: "Schreibe wie ich" — zieht Samples aus journal/notes/articles, extrahiert Prinzipien
- Pro Style: Preview-Box "So klingt's: [Beispiel-Absatz über Dummy-Topic]" — lazy generiert auf Klick
## Style-System — Details
### Preset-Library (`presets/styles.ts`)
Erste Tranche:
- **Akademisch** — dicht, passive Voice erlaubt, Zitate, Konjunktiv
- **Casual Blog** — du-Ansprache, kurze Absätze, rhetorische Fragen
- **LinkedIn-Post** — Hook in Zeile 1, 1-Satz-Absätze, Emoji sparsam, Call-to-action am Ende
- **Twitter/X-Thread** — nummerierte Tweets, je ≤280 Chars, Cliffhanger
- **Hemingway** — deklarativ, kurze Sätze, minimal Adjektive
- **Nachrichtlich** — inverted pyramid, nüchtern, keine Meinung
- **Buzzfeed-Listicle** — Listenformat, überspitzte Einleitungen
- **Pitch / Sales** — Problem → Agitation → Solution-Struktur
- **Memoir** — 1. Person, sensorisch, Szenen statt Zusammenfassungen
### Space-Default-Style
- Personal-Spaces: kein Default; User wählt pro Draft (oder "Schreibe wie ich" ist Default nach erstem Self-Training).
- Team/Firmen-Spaces: `spaceDefaultStyleId` im `Space`-Record (Erweiterung in `spaces-foundation`). Ein Space-Admin kann einen Style als `isSpaceDefault=true` markieren.
- Vererbung: Briefing.styleId → Space-Default-Style → Kein Style (AI wählt generisch).
### Persona-Linkage
`mana-persona-runner` Personas bekommen ein optionales `defaultWritingStyleId`. Wenn eine Persona einen Writing-Draft erzeugt (via MCP `create_draft`-Tool), wird ihr Default-Style vorausgewählt. Personas und Styles bleiben getrennte Entitäten — die Linkage ist lose.
## AI-Integration
### Tools (`tools.ts` + `@mana/shared-ai`)
| Tool | Policy | Beschreibung |
|---|---|---|
| `list_drafts` | auto | Liefert Drafts gefiltert nach `kind`/`status`, read-only |
| `get_draft` | auto | Voller Draft inkl. aktueller Version |
| `create_draft` | propose | Legt neuen Draft mit Briefing an (ohne Generation) |
| `generate_draft_content` | propose | Startet Generation auf existierendem Draft → schreibt neue Version |
| `generate_outline` | propose | Generiert Outline aus Briefing, als "Outline"-Section vor Volltext |
| `refine_selection` | propose | Mark. Passage umschreiben mit Instruction |
| `shorten_draft` | propose | Verkürzen auf Ziel-Wortzahl |
| `expand_draft` | propose | Ausweiten auf Ziel-Wortzahl |
| `change_tone` | propose | Ton wechseln |
| `translate_draft` | propose | In andere Sprache übersetzen — erstellt neuen Draft mit `language` und Link auf Original |
| `publish_draft` | propose | Nach website/articles/... veröffentlichen |
| `list_writing_styles` | auto | Alle verfügbaren Styles (Preset + Custom) |
| `train_style_from_samples` | propose | Neuen Custom-Style aus Sample-Set extrahieren |
Alle `propose`-Tools landen in `ProposalInbox` mit Preview (Diff gegen aktuellen Content bei Refine-Tools).
### Provider-Wahl (Runtime)
| Fall | Provider |
|---|---|
| Kurztext (≤300 Wörter), synchron gewünscht | `mana-llm` direkt (oder `local-llm` als Fallback) |
| Langtext (>300 Wörter) | Mission über `mana-ai` — streamt zurück, versions-fähig |
| Offline / Privacy-max | `local-llm` (Gemma 4 E2B via WebGPU) — Qualität eingeschränkt |
| Mit Recherche-Flag | Mission über `mana-ai` mit pre-planning web-research-Injection (analog zu `news-research`-Keywords) |
Die Entscheidung passiert im `generations.svelte.ts`-Store, nicht im Tool. Tools sind Provider-agnostisch.
### Mission-Flow für Langtext
1. `generate_draft_content` erstellt `LocalGeneration` mit `status='queued'`, provider=`mana-ai`
2. Store startet Mission über `mana-ai` mit Context: Briefing + Style (inkl. `extractedPrinciples`) + Referenzen (aufgelöst zu voll­text wo möglich) + Space-Kontext-Docs falls vorhanden
3. Mission-Runner kettet intern bis zu 5 Planner-Calls:
- Research (optional, falls Referenzen URLs enthalten ohne Inhalt)
- Outline (falls `generate_outline` separat gecalled oder automatisch bei langen Texten)
- Volltext-Generation
- Selbst-Review (optional — Qualitätscheck)
- Final Polish
4. Streaming-Output landet via Sync-Channel im Client-Store → UI zeigt live
5. Bei `status='succeeded'`: `applyGenerationAsVersion(generationId)` schreibt neue `LocalDraftVersion`, setzt `currentVersionId`
### Recherche-Integration
- Flag `briefing.useResearch: boolean` (im UI "Mit Web-Recherche schreiben")
- Wenn gesetzt, injectet mana-ai bei Mission-Start `mana-research` pre-planning (existing Code aus `news-research`)
- Gefundene Quellen werden automatisch als `DraftReference[]` mit `kind='url'` an den Draft gehängt
- Inline-Zitate optional als M7-Feature (Markdown-Footnotes)
## Cross-Modul Integration
### Als Konsument
| Modul | Integration |
|---|---|
| `articles` | Als Referenz pickbar; Content fließt in Prompt |
| `notes` | Als Referenz pickbar |
| `library` | Entries als Referenz ("schreibe über Film X") |
| `kontext` | Kontext-Docs als Standing-Context, Space-Default-Referenzen |
| `goals` | Als Motivation-Anker ("Ziel-Update-Post") |
| `me-images` | Für Ghost-Writer mit Foto: picture-Generation eines Headers vor-/nach-geschaltet |
| `mana-research` | Bei `useResearch=true` automatisch |
### Als Produzent
| Ziel-Modul | Publish-Hook |
|---|---|
| `website` | Draft → neuer Text-Block in ausgewählter Page |
| `articles` | Als "Eigen-Artikel" speichern (mit Autor=Self) |
| `social-relay` | Zu Social-Plattformen senden (falls Modul aktiv) |
| `mail` | Als E-Mail-Entwurf übergeben |
| `presi` | Als Präsi-Outline-Import |
| Export | Markdown-Download, PDF, Zwischenablage |
Publish-Targets werden in `draft.publishedTo[]` gespeichert → User sieht "Wurde veröffentlicht als: ..." im DetailView.
## Events (Domain-Events)
Für Workbench-Timeline + Audit:
- `WritingDraftCreated`
- `WritingDraftBriefingUpdated`
- `WritingDraftGenerationStarted` (für live-Tracking)
- `WritingDraftVersionCreated`
- `WritingDraftVersionReverted`
- `WritingDraftPublished`
- `WritingDraftVisibilityChanged`
- `WritingStyleCreated`
- `WritingStyleTrainedFromSamples`
## Registrierung (Checklist)
1. `module.config.ts` anlegen mit `tables: [drafts, draftVersions, generations, writingStyles]`
2. Config in `apps/mana/apps/web/src/lib/data/module-registry.ts` aufnehmen
3. Dexie-Schema-Migration: neue Version mit vier Tables
4. Encryption-Registry: vier Einträge
5. Routes unter `(app)/writing/` anlegen
6. App-Eintrag in `packages/shared-branding/src/mana-apps.ts`:
```typescript
{ id: 'writing', name: 'Writing', description: {...}, icon: APP_ICONS.writing, color: '#0ea5e9', status: 'development', requiredTier: 'beta' }
```
7. Icon in `packages/shared-branding/src/app-icons.ts`
8. `docs/MODULE_REGISTRY.md` ergänzen
9. Guest-Seed in `collections.ts` (1 Draft + 1 leerer Custom-Style)
10. Vitest für Mutationen + Encryption-Roundtrip + Version-Logik
## Offene Fragen
- **Outline-Mandatory?** Für Blog/Essay ist eine Outline fast immer sinnvoll; für Social/Bio/E-Mail-Kurz nicht. **Vorschlag:** `AUTO_OUTLINE_KINDS = ['blog', 'essay', 'speech', 'cover-letter', 'story']` — bei denen startet die Mission mit Outline-Schritt automatisch. User kann im Briefing überschreiben.
- **Image-Integration mit `picture`:** Soll ein Draft optional einen Header/Cover-Image haben, generiert via picture? **Vorschlag:** erst M9+. Zunächst nur `coverImageId` als optionales Feld reservieren (plaintext FK) — UI kommt später.
- **Kollaboratives Editing:** Mehrere User im gleichen Space editieren denselben Draft. Sync-Layer ist LWW → letzte Änderung gewinnt. Das reicht für den Anfang. Realtime-CRDT ist kein Phase-1-Thema.
- **Auto-Title:** Soll der Title aus dem Topic automatisch gesetzt werden oder beim ersten Generate aus dem generierten Text extrahiert? **Vorschlag:** Topic = initialer Titel; beim ersten Draft-Version-Create bietet die UI "Titel vom AI vorschlagen lassen" an.
- **Re-Generate-Semantik:** Ersetzt eine volle Re-Generation die vorherige Version oder fügt neue hinzu? Wir haben entschieden "neue Version immer" — das kann aber bei 10 Iterationen unübersichtlich werden. **Vorschlag:** History-Panel zeigt nur `isAiGenerated=true`-Versions mit Label "Generation N"; "Zwischenstände" (Selection-Apply) bleiben im lokalen Undo-Stack ohne Version-Record.
- **Token-Limits bei großen Referenzen:** Lange Artikel als Referenz → Prompt-Explosion. **Vorschlag:** Im Mission-Runner automatischen Reference-Summarizer davorschalten (schon für `articles` da? prüfen). Falls nicht, als Sub-Task in M7.
- **Veröffentlichte Drafts readonly?** Nach `publish_draft` sollte der Draft vor versehentlichem Editieren geschützt sein. **Vorschlag:** Status `published` → UI rendert text readonly mit "Editieren erlauben"-Toggle; Publish-Targets zeigen Sync-Status.
## Reihenfolge (Milestones)
1. **M1 — Skelett**: types, collections, module.config, Registrierung, Dexie-Migration (v N+1), leere Routes, leeres ListView, kein UI. *Ziel: App zeigt "Writing"-Modul-Kachel an, Route lädt leer, nichts crasht.*
2. **M2 — Draft-CRUD manuell**: `createDraft`, `BriefingForm`, `DraftCard`, `KindTabs`, `DetailView` mit manuell editierbarem Text (ohne AI). Alle 12 Kinds als Chips. *Ziel: User kann Drafts anlegen und tippen — wie ein eingebauter Texteditor.*
3. **M3 — Generation v1 (Sync-LLM)**: `generate_draft_content` über `mana-llm` direkt, ohne Mission-Runner. Schreibt neue `LocalDraftVersion`. Versions-History-Panel. *Ziel: "Generate"-Button produziert ersten Draft-Text aus Briefing für Kurztexte.*
4. **M4 — Stil-System (Presets + Custom)**: `LocalWritingStyle`-Table, 9 Presets, `/writing/styles` View, `StylePicker` in Briefing, Style fließt in Prompt ein. *Ziel: User wählt "LinkedIn-Post"-Preset und Output ändert sich sichtbar.*
5. **M4.1 — "Schreibe wie ich" (Self-Training)**: `train_style_from_samples` mit Auto-Pull aus `journal` + `notes` + `articles`. Extrahierte Prinzipien gecached. *Ziel: Ein "Self"-Style, der User's Schreibstil imitiert.*
6. **M5 — Cross-Modul-Referenzen**: `ReferencePicker`, Auflösung in Prompt-Context mit Summarizer bei Langtext. *Ziel: "Schreibe Blog über Buch X (aus library) und Artikel Y (aus articles)".*
7. **M6 — Selection-Refinement-Tools**: `SelectionToolbar`, `refine_selection` / `shorten` / `expand` / `change_tone` als Selection-Operations mit Diff-Preview. Undo-Stack lokal. *Ziel: User markiert Absatz, klickt "Kürzer" → 3 Optionen als Proposal, User picked.*
8. **M7 — Mission-Runner für Langtext + Recherche**: Flip auf `mana-ai`-Missions für lange Drafts, `useResearch`-Flag, Outline-Stage, Streaming-Preview. *Ziel: Essay >1500 Wörter mit Outline→Draft→Review in einer Mission.*
9. **M8 — AI-Tool-Katalog + MCP-Exposure**: Alle Tools in `@mana/shared-ai/src/tools/schemas.ts`, in `mana-mcp` exposed, `AiProposalInbox` im DetailView. Persona-Linkage (`defaultWritingStyleId`). *Ziel: Personas können Drafts erzeugen, Claude Desktop hat Writing-Tools.*
10. **M9 — Canvas-Modus** (optional, Phase 2): Inline-Autocomplete am Cursor, `/`-Command-Palette wie Notion AI. Gleiche Draft-Datenstruktur, alternative UX. *Ziel: User tippt im leeren Canvas, AI ergänzt kontinuierlich.*
11. **M10 — Publish-Hooks**: Integration mit `website`, `articles`, `presi`, `social-relay`. Markdown/PDF-Export. *Ziel: Ein Draft kann als Block auf Website gepublisht werden mit einem Klick.*
12. **M11 — Visibility-System adoptieren**: `<VisibilityPicker>` in DetailView, Unlisted-Share-Link, Embed-Support auf Website. *Ziel: Writing konform mit Visibility-M1+-Standard.*
M1M3 sind "Grundfunktion steht". Ab M4 wird's differenzierend. M7 macht es gegenüber ChatGPT einzigartig (Space-Kontext + Cross-Modul-Refs + Mission-Chaining). M9 ist "nice-to-have, wenn Ghostwriter-Flow sich als zu starr erweist".