diff --git a/frontend/src/lib/components/document/DocumentEditLayout.svelte b/frontend/src/lib/components/document/DocumentEditLayout.svelte
new file mode 100644
index 00000000..8fe0664f
--- /dev/null
+++ b/frontend/src/lib/components/document/DocumentEditLayout.svelte
@@ -0,0 +1,221 @@
+
+
+
+
+
+ {@render topbar()}
+
+
+
+
+
Pflichtfelder
+
+
{requiredFilled} / 3
+
+
+
+
+
+
+ {#if !doc.filePath}
+
+ {:else}
+
+
+
+
+
+ {}}
+ />
+
+ {/if}
+
+
+
+
+ {#if formError}
+
+ {formError}
+
+ {/if}
+
+
+
+
+
+ {@render actionbar()}
+
+
+
+
diff --git a/frontend/src/lib/components/document/WhoWhenSection.svelte b/frontend/src/lib/components/document/WhoWhenSection.svelte
index cddd9514..6a1dfe2a 100644
--- a/frontend/src/lib/components/document/WhoWhenSection.svelte
+++ b/frontend/src/lib/components/document/WhoWhenSection.svelte
@@ -4,12 +4,9 @@ import PersonTypeahead from '$lib/components/PersonTypeahead.svelte';
import PersonMultiSelect from '$lib/components/PersonMultiSelect.svelte';
import { isoToGerman, handleGermanDateInput } from '$lib/utils/date';
import { m } from '$lib/paraglide/messages.js';
+import type { components } from '$lib/generated/api';
-interface Person {
- id: string;
- firstName: string;
- lastName: string;
-}
+type Person = components['schemas']['Person'];
let {
senderId = $bindable(''),
diff --git a/frontend/src/routes/documents/[id]/edit/+page.server.ts b/frontend/src/routes/documents/[id]/edit/+page.server.ts
index de11c47a..f8e28fe5 100644
--- a/frontend/src/routes/documents/[id]/edit/+page.server.ts
+++ b/frontend/src/routes/documents/[id]/edit/+page.server.ts
@@ -6,12 +6,15 @@ import { parseBackendError, getErrorMessage } from '$lib/errors';
export async function load({
params,
fetch,
- locals
+ locals,
+ depends
}: {
params: { id: string };
fetch: typeof globalThis.fetch;
locals: App.Locals;
+ depends: (dep: string) => void;
}) {
+ depends('app:document');
const canWrite =
locals.user?.groups?.some((g: { permissions: string[] }) =>
g.permissions.includes('WRITE_ALL')
diff --git a/frontend/src/routes/documents/[id]/edit/+page.svelte b/frontend/src/routes/documents/[id]/edit/+page.svelte
index 6bc970f1..936407b4 100644
--- a/frontend/src/routes/documents/[id]/edit/+page.svelte
+++ b/frontend/src/routes/documents/[id]/edit/+page.svelte
@@ -1,27 +1,34 @@
-
-
-
+
+ {doc.title || doc.originalFilename || 'Dokument'} — {m.doc_edit_heading()}
+
+
+
+ {#snippet topbar()}
{m.btn_back_to_document()}
-
- {m.doc_edit_heading()} —
- {doc.title || doc.originalFilename}
-
-
- {#if form?.error}
-
{form.error}
- {/if}
+
+ {doc.title || doc.originalFilename}
+
-
+
+ {/snippet}
-
-
-
+ {#snippet actionbar()}
+
+
+
+ {/snippet}
+
+
+
+
diff --git a/frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte b/frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte
deleted file mode 100644
index 6a30be46..00000000
--- a/frontend/src/routes/documents/[id]/edit/FileSectionEdit.svelte
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
- {m.doc_section_file()}
-
-
-
-
-
-

-
{m.doc_current_file_label()}
- {originalFilename}
-
-
-
-
-
-
diff --git a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte b/frontend/src/routes/documents/[id]/edit/SaveBar.svelte
deleted file mode 100644
index 477f3582..00000000
--- a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts b/frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts
deleted file mode 100644
index d1ad0d97..00000000
--- a/frontend/src/routes/documents/[id]/edit/SaveBar.svelte.spec.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { describe, it, expect, vi, afterEach } from 'vitest';
-import { cleanup, render } from 'vitest-browser-svelte';
-import { page } from 'vitest/browser';
-import SaveBar from './SaveBar.svelte';
-import { createConfirmService, CONFIRM_KEY } from '$lib/services/confirm.svelte.js';
-
-let appendedForms: HTMLFormElement[] = [];
-
-afterEach(() => {
- cleanup();
- appendedForms.forEach((f) => f.remove());
- appendedForms = [];
-});
-
-function renderSaveBar(docId = 'doc-1') {
- const service = createConfirmService();
-
- // Mount a dummy delete form so SaveBar can find it via document.getElementById
- const deleteForm = document.createElement('form');
- deleteForm.id = 'delete-form';
- document.body.appendChild(deleteForm);
- appendedForms.push(deleteForm);
-
- const result = render(SaveBar, {
- props: { docId },
- context: new Map([[CONFIRM_KEY, service]])
- });
-
- return { ...result, service, deleteForm };
-}
-
-// ─── Rendering ────────────────────────────────────────────────────────────────
-
-describe('SaveBar — rendering', () => {
- it('renders save button', async () => {
- renderSaveBar();
- await expect.element(page.getByRole('button', { name: /Speichern/i })).toBeInTheDocument();
- });
-
- it('renders delete button', async () => {
- renderSaveBar();
- // The delete button should be type="button" (async confirm flow)
- const deleteBtn = document.querySelector('button[type="button"]');
- expect(deleteBtn).not.toBeNull();
- });
-
- it('renders cancel link pointing to /documents/doc-1', async () => {
- renderSaveBar();
- await expect
- .element(page.getByRole('link', { name: /Abbrechen/i }))
- .toHaveAttribute('href', '/documents/doc-1');
- });
-});
-
-// ─── Delete confirmation ──────────────────────────────────────────────────────
-
-describe('SaveBar — delete confirmation', () => {
- it('opens confirm dialog when delete button is clicked', async () => {
- const { service } = renderSaveBar();
- const deleteBtn = document.querySelectorAll('button[type="button"]')[0];
- deleteBtn.click();
- await vi.waitFor(() => expect(service.options).not.toBeNull());
- expect(service.options?.destructive).toBe(true);
- service.settle(false);
- });
-
- it('submits delete form when user confirms', async () => {
- const { service, deleteForm } = renderSaveBar();
- const requestSubmit = vi.spyOn(deleteForm, 'requestSubmit').mockImplementation(() => {});
-
- const deleteBtn = document.querySelectorAll('button[type="button"]')[0];
- deleteBtn.click();
- await vi.waitFor(() => expect(service.options).not.toBeNull());
- service.settle(true);
- await vi.waitFor(() => expect(service.options).toBeNull());
-
- expect(requestSubmit).toHaveBeenCalledOnce();
- });
-
- it('does not submit delete form when user cancels', async () => {
- const { service, deleteForm } = renderSaveBar();
- const requestSubmit = vi.spyOn(deleteForm, 'requestSubmit').mockImplementation(() => {});
-
- const deleteBtn = document.querySelectorAll('button[type="button"]')[0];
- deleteBtn.click();
- await vi.waitFor(() => expect(service.options).not.toBeNull());
- service.settle(false);
- await vi.waitFor(() => expect(service.options).toBeNull());
-
- expect(requestSubmit).not.toHaveBeenCalled();
- });
-});
diff --git a/frontend/src/routes/enrich/[id]/+page.svelte b/frontend/src/routes/enrich/[id]/+page.svelte
index 2bd05b74..81d72d49 100644
--- a/frontend/src/routes/enrich/[id]/+page.svelte
+++ b/frontend/src/routes/enrich/[id]/+page.svelte
@@ -1,101 +1,19 @@
{doc.title || doc.originalFilename || 'Dokument'} — Anreicherung
-
-
-
+ {/snippet}
-
-
-
Pflichtfelder
-
-
{requiredFilled} / 3
-
+ {m.enrich_skip()}
+
-
-
-
-
- {#if !doc.filePath}
-
- {:else}
-
-
-
-
-
- {}}
- />
-
- {/if}
-
-
-
-
- {#if form?.error}
-
- {form.error}
-
- {/if}
-
-
+ {m.btn_save()}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+ {/snippet}
+
+
+