diff --git a/apps/mana/apps/web/src/lib/modules/writing/components/BriefingForm.svelte b/apps/mana/apps/web/src/lib/modules/writing/components/BriefingForm.svelte
index 5da6fce58..5ebb07f7b 100644
--- a/apps/mana/apps/web/src/lib/modules/writing/components/BriefingForm.svelte
+++ b/apps/mana/apps/web/src/lib/modules/writing/components/BriefingForm.svelte
@@ -7,6 +7,7 @@
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/writing/components/StylePicker.svelte b/apps/mana/apps/web/src/lib/modules/writing/components/StylePicker.svelte
new file mode 100644
index 000000000..1e48d59aa
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/writing/components/StylePicker.svelte
@@ -0,0 +1,68 @@
+
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/modules/writing/stores/generations.svelte.ts b/apps/mana/apps/web/src/lib/modules/writing/stores/generations.svelte.ts
index ff0e26e7f..a92784abc 100644
--- a/apps/mana/apps/web/src/lib/modules/writing/stores/generations.svelte.ts
+++ b/apps/mana/apps/web/src/lib/modules/writing/stores/generations.svelte.ts
@@ -20,7 +20,7 @@ import { emitDomainEvent } from '$lib/data/events';
import { generationTable, draftTable, draftVersionTable, writingStyleTable } from '../collections';
import { callWritingGeneration } from '../api';
import { buildDraftPrompt, estimateMaxTokens } from '../utils/prompt-builder';
-import { getStylePreset } from '../presets/styles';
+import { getStylePreset, type StylePreset } from '../presets/styles';
import type {
LocalDraftVersion,
LocalGeneration,
@@ -37,10 +37,25 @@ function wordCountOf(text: string): number {
return trimmed.split(/\s+/).length;
}
-async function loadStyle(styleId: string | null | undefined): Promise {
+/**
+ * Resolve the `draft.styleId` reference. A draft can point at either a
+ * preset (serialised as `preset:`, no Dexie row needed) or a custom
+ * WritingStyle row (uuid). Presets are static code, so no DB write is
+ * required for first-time selection — the picker just sets the id.
+ */
+async function loadStyle(
+ styleId: string | null | undefined
+): Promise<
+ { source: 'preset'; preset: StylePreset } | { source: 'custom'; row: LocalWritingStyle } | null
+> {
if (!styleId) return null;
+ if (styleId.startsWith('preset:')) {
+ const preset = getStylePreset(styleId.slice('preset:'.length));
+ return preset ? { source: 'preset', preset } : null;
+ }
const row = await writingStyleTable.get(styleId);
- return row && !row.deletedAt ? row : null;
+ if (!row || row.deletedAt) return null;
+ return { source: 'custom', row };
}
async function nextVersionNumber(draftId: string): Promise {
@@ -76,16 +91,22 @@ export const generationsStore = {
(await draftVersionTable.get(draft.currentVersionId))?.content?.trim()
? 'full-regenerate'
: 'draft-from-brief';
- const style = await loadStyle(draft.styleId);
+ const resolved = await loadStyle(draft.styleId);
const stylePreset =
- style?.source === 'preset' && style.presetId ? getStylePreset(style.presetId) : undefined;
+ resolved?.source === 'preset'
+ ? resolved.preset
+ : resolved?.source === 'custom' && resolved.row.presetId
+ ? getStylePreset(resolved.row.presetId)
+ : undefined;
+ const styleExtracted =
+ resolved?.source === 'custom' ? (resolved.row.extractedPrinciples ?? undefined) : undefined;
const { system, user } = buildDraftPrompt({
kind: draft.kind,
title: draft.title,
briefing: draft.briefing,
stylePreset,
- styleExtracted: style?.extractedPrinciples ?? undefined,
+ styleExtracted,
});
const maxTokens = opts.maxTokens ?? estimateMaxTokens(draft.briefing);
diff --git a/apps/mana/apps/web/src/lib/modules/writing/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/writing/views/DetailView.svelte
index 595b7fd91..160c654e7 100644
--- a/apps/mana/apps/web/src/lib/modules/writing/views/DetailView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/writing/views/DetailView.svelte
@@ -20,8 +20,10 @@
useVersionsForDraft,
useCurrentVersionForDraft,
useGenerationsForDraft,
+ useAllStyles,
} from '../queries';
import { KIND_LABELS, STATUS_LABELS } from '../constants';
+ import { getStylePreset } from '../presets/styles';
import type { DraftStatus } from '../types';
let { id }: { id: string } = $props();
@@ -109,6 +111,22 @@
const kind = $derived(draft ? KIND_LABELS[draft.kind] : null);
const targetWords = $derived(draft?.briefing.targetLength?.value ?? null);
const STATUS_ORDER: DraftStatus[] = ['draft', 'refining', 'complete', 'published'];
+
+ // Resolve the active style's display name: preset ids are static
+ // code; custom ids are looked up in the reactive styles list. Falls
+ // back to null when the draft has no style set (ad-hoc).
+ const allStyles$ = useAllStyles();
+ const allStyles = $derived(allStyles$.value);
+ const activeStyleName = $derived.by(() => {
+ const sid = draft?.styleId;
+ if (!sid) return null;
+ if (sid.startsWith('preset:')) {
+ const preset = getStylePreset(sid.slice('preset:'.length));
+ return preset ? preset.name.de : null;
+ }
+ const custom = allStyles.find((s) => s.id === sid);
+ return custom ? custom.name : null;
+ });
{#if draft$.loading}
@@ -163,6 +181,9 @@
{briefingOpen ? '▾' : '▸'} Briefing
{#if !briefingOpen}
{draft.briefing.topic}
+ {#if activeStyleName}
+ 🎨 {activeStyleName}
+ {/if}
{/if}
{#if briefingOpen}
@@ -367,6 +388,15 @@
min-width: 0;
flex: 1;
}
+ .style-chip {
+ font-size: 0.75rem;
+ padding: 0.1rem 0.5rem;
+ border-radius: 999px;
+ background: color-mix(in srgb, #0ea5e9 10%, transparent);
+ color: #0ea5e9;
+ font-weight: normal;
+ flex-shrink: 0;
+ }
.columns {
display: grid;
grid-template-columns: 1fr 280px;
diff --git a/apps/mana/apps/web/src/lib/modules/writing/views/ListView.svelte b/apps/mana/apps/web/src/lib/modules/writing/views/ListView.svelte
index e0777a69b..20adfee7c 100644
--- a/apps/mana/apps/web/src/lib/modules/writing/views/ListView.svelte
+++ b/apps/mana/apps/web/src/lib/modules/writing/views/ListView.svelte
@@ -97,6 +97,7 @@
bind:value={searchQuery}
placeholder="Nach Titel oder Thema suchen…"
/>
+ 🎨 Stile