feat(stellungnahme): Notizfeld pro Argument + editierbare Einleitung/Abschluss
Deploy / verify (push) Failing after 50s
Deploy / deploy (push) Has been skipped

- Notizfeld an jedem Argument (expanded) → erscheint im Output als "Anmerkung:"
- Einleitung und Abschluss in Step 5 via <details> editierbar
- Beide mit Zurücksetzen-Button auf Default
- Alles in localStorage persistiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-28 13:27:07 +02:00
parent e239c85961
commit d45e691171
@@ -105,13 +105,15 @@
absenderName,
absenderAnschrift,
absenderOrt,
argNotes,
openingOverride,
closingOverride,
}));
} catch {}
}
$effect(() => {
// track all persisted fields reactively
void [selectedSlugs, persoenlicheEingabe, absenderName, absenderAnschrift, absenderOrt];
void [selectedSlugs, persoenlicheEingabe, absenderName, absenderAnschrift, absenderOrt, argNotes, openingOverride, closingOverride];
if (mounted) saveState();
});
@@ -138,6 +140,22 @@
let absenderOrt = $state("");
let showOutput = $state(false);
// Per-argument notes (appear in output after bullet points)
let argNotes = $state<Record<string, string>>({});
function setArgNote(slug: string, value: string) {
argNotes = { ...argNotes, [slug]: value };
}
// Editable opening/closing — null = use reactive default
const defaultOpening = $derived(
`ich erhebe Einwendung gegen die Ausweisung des Windenergie-Vorranggebiets ${gebietsBez} im 2. Entwurf des Regionalplans Südwestthüringen.`,
);
const defaultClosing = "Ich fordere die Regionalplanung auf, das Vorranggebiet aus dem Regionalplan zu streichen oder zumindest einer erneuten, umfassenden Überprüfung zu unterziehen.";
let openingOverride = $state<string | null>(null);
let closingOverride = $state<string | null>(null);
const effectiveOpening = $derived(openingOverride ?? defaultOpening);
const effectiveClosing = $derived(closingOverride ?? defaultClosing);
function toggleSlug(slug: string) {
const next = new Set(selectedSlugs);
@@ -221,7 +239,7 @@ Karl-Liebknecht-Straße 4
Stichpunkte müssen vor dem Einreichen in eigene, ausformulierte
Sätze überführt werden. Text individuell anpassen.
${"=".repeat(64)}`;
let body = `ich erhebe Einwendung gegen die Ausweisung des Windenergie-Vorranggebiets ${gebietsBez} im 2. Entwurf des Regionalplans Südwestthüringen.\n\n`;
let body = `${effectiveOpening}\n\n`;
for (const f of selectedFragments) {
if (f.title) body += `${f.title}\n\n`;
if (f.bulletPoints?.length) {
@@ -230,10 +248,12 @@ ${"=".repeat(64)}`;
} else if (f.text) {
body += `${stripMd(f.text)}\n\n`;
}
const note = argNotes[f._slug]?.trim();
if (note) body += `Anmerkung: ${note}\n\n`;
}
if (persoenlicheEingabe.trim())
body += `Persönliche Betroffenheit\n\n${persoenlicheEingabe.trim()}\n\n`;
body += `Ich fordere die Regionalplanung auf, das Vorranggebiet aus dem Regionalplan zu streichen oder zumindest einer erneuten, umfassenden Überprüfung zu unterziehen.\n\n${closing}`;
body += `${effectiveClosing}\n\n${closing}`;
return [disclaimer, "", sender, "", heute, "", "An:", RECIPIENT, "", "Betreff: " + betreff.split("\n")[0], betreff.split("\n")[1] ?? "", "", "Sehr geehrte Damen und Herren,", "", body.trimEnd()]
.join("\n")
@@ -276,6 +296,9 @@ ${"=".repeat(64)}`;
selectedSlugs = new Set();
expandedItems = new Set();
expandedGroups = new Set();
argNotes = {};
openingOverride = null;
closingOverride = null;
persoenlicheEingabe = "";
absenderName = "";
absenderAnschrift = "";
@@ -385,6 +408,9 @@ ${"=".repeat(64)}`;
if (typeof s.persoenlicheEingabe === "string") persoenlicheEingabe = s.persoenlicheEingabe;
if (typeof s.absenderName === "string") absenderName = s.absenderName;
if (typeof s.absenderAnschrift === "string") absenderAnschrift = s.absenderAnschrift;
if (typeof s.absenderOrt === "string") absenderOrt = s.absenderOrt;
if (s.argNotes && typeof s.argNotes === "object") argNotes = s.argNotes;
if (typeof s.openingOverride === "string") openingOverride = s.openingOverride;
if (typeof s.closingOverride === "string") closingOverride = s.closingOverride;
if ((s.selectedSlugs?.length ?? 0) > 0 || s.absenderName || s.persoenlicheEingabe) {
hadSavedState = true;
@@ -637,9 +663,18 @@ ${"=".repeat(64)}`;
{/if}
</div>
</div>
</div>
{#if k.text && expandedItems.has(k._slug)}
</div>
{#if expandedItems.has(k._slug)}
<div class="border-t border-stein-100 px-3 pb-3 pt-2 space-y-2">
{#if k.text}
<div class="prose prose-sm prose-stein max-w-none text-sm text-stein-700">{@html md(k.text)}</div>
{/if}
<textarea
class="w-full rounded border border-stein-200 px-2 py-1.5 text-xs text-stein-700 placeholder:text-stein-400 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200 resize-none"
rows="2"
placeholder="Persönliche Notiz zu diesem Punkt (erscheint im Dokument) …"
value={argNotes[k._slug] ?? ""}
oninput={(e) => setArgNote(k._slug, (e.target as HTMLTextAreaElement).value)}
></textarea>
</div>
{/if}
@@ -729,9 +764,18 @@ ${"=".repeat(64)}`;
{/if}
</div>
</div>
</div>
{#if f.text && expandedItems.has(f._slug)}
</div>
{#if expandedItems.has(f._slug)}
<div class="border-t border-stein-100 px-3 pb-3 pt-2 space-y-2">
{#if f.text}
<div class="prose prose-sm prose-stein max-w-none text-sm text-stein-700">{@html md(f.text)}</div>
{/if}
<textarea
class="w-full rounded border border-stein-200 px-2 py-1.5 text-xs text-stein-700 placeholder:text-stein-400 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200 resize-none"
rows="2"
placeholder="Persönliche Notiz zu diesem Punkt (erscheint im Dokument) …"
value={argNotes[f._slug] ?? ""}
oninput={(e) => setArgNote(f._slug, (e.target as HTMLTextAreaElement).value)}
></textarea>
</div>
{/if}
@@ -810,6 +854,44 @@ ${"=".repeat(64)}`;
<div class="mb-1 font-semibold text-stein-700">Einzureichen bei:</div>
<div class="whitespace-pre">{RECIPIENT}</div>
</div>
<!-- Einleitung & Abschluss anpassen -->
<div class="mb-4 space-y-2">
<details class="rounded-lg border border-stein-200 overflow-hidden">
<summary class="flex cursor-pointer items-center justify-between bg-stein-50 px-4 py-2.5 text-sm font-medium text-stein-700 hover:bg-stein-100 list-none">
<span>Einleitung anpassen</span>
<Icon icon="mdi:chevron-down" class="size-4 text-stein-400" />
</summary>
<div class="border-t border-stein-100 px-4 py-3">
<textarea
class="w-full rounded border border-stein-200 px-3 py-2 text-sm text-stein-800 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200 resize-y"
rows="3"
value={effectiveOpening}
oninput={(e) => (openingOverride = (e.target as HTMLTextAreaElement).value)}
></textarea>
{#if openingOverride !== null}
<button class="mt-1 text-xs text-stein-400 hover:text-stein-600" onclick={() => (openingOverride = null)}>Zurücksetzen</button>
{/if}
</div>
</details>
<details class="rounded-lg border border-stein-200 overflow-hidden">
<summary class="flex cursor-pointer items-center justify-between bg-stein-50 px-4 py-2.5 text-sm font-medium text-stein-700 hover:bg-stein-100 list-none">
<span>Abschluss anpassen</span>
<Icon icon="mdi:chevron-down" class="size-4 text-stein-400" />
</summary>
<div class="border-t border-stein-100 px-4 py-3">
<textarea
class="w-full rounded border border-stein-200 px-3 py-2 text-sm text-stein-800 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200 resize-y"
rows="3"
value={effectiveClosing}
oninput={(e) => (closingOverride = (e.target as HTMLTextAreaElement).value)}
></textarea>
{#if closingOverride !== null}
<button class="mt-1 text-xs text-stein-400 hover:text-stein-600" onclick={() => (closingOverride = null)}>Zurücksetzen</button>
{/if}
</div>
</details>
</div>
<div class="mb-2 flex gap-3">
<button