diff --git a/frontend/src/lib/document/DocumentDate.svelte b/frontend/src/lib/document/DocumentDate.svelte
new file mode 100644
index 00000000..a3539c31
--- /dev/null
+++ b/frontend/src/lib/document/DocumentDate.svelte
@@ -0,0 +1,60 @@
+
+
+
+
+ {#if isUnknown}
+
+
+ {/if}
+ {label}
+
+ {#if showRawLine}
+
+ {m.date_original_label()} {raw}
+ {/if}
+
diff --git a/frontend/src/lib/document/DocumentDate.svelte.test.ts b/frontend/src/lib/document/DocumentDate.svelte.test.ts
new file mode 100644
index 00000000..fa842b7b
--- /dev/null
+++ b/frontend/src/lib/document/DocumentDate.svelte.test.ts
@@ -0,0 +1,35 @@
+import { describe, it, expect, afterEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import { page } from 'vitest/browser';
+import DocumentDate from './DocumentDate.svelte';
+
+// Browser-project (Playwright) tests — CI only.
+
+afterEach(cleanup);
+
+describe('DocumentDate', () => {
+ it('renders a DAY date as a full long date', async () => {
+ render(DocumentDate, { props: { iso: '1943-12-24', precision: 'DAY' } });
+ await expect.element(page.getByText('24. Dezember 1943')).toBeInTheDocument();
+ });
+
+ it('renders MONTH precision as month + year, never a day', async () => {
+ render(DocumentDate, { props: { iso: '1916-06-01', precision: 'MONTH', raw: 'Juni 1916' } });
+ await expect.element(page.getByText('Juni 1916')).toBeInTheDocument();
+ });
+
+ it('shows the verbatim raw cell as a visible secondary line for UNKNOWN (not tooltip-only)', async () => {
+ render(DocumentDate, { props: { iso: null, precision: 'UNKNOWN', raw: 'Sommer?' } });
+ // Real, visible text — not hidden behind a title attribute.
+ await expect.element(page.getByText('Datum unbekannt')).toBeInTheDocument();
+ await expect.element(page.getByText(/Sommer\?/)).toBeVisible();
+ });
+
+ it('renders a malicious raw value as inert escaped text (no element injected)', async () => {
+ const malicious = '
';
+ render(DocumentDate, { props: { iso: null, precision: 'UNKNOWN', raw: malicious } });
+ // The payload appears as literal text, and no
is created in the DOM.
+ await expect.element(page.getByText(/![]()