diff --git a/frontend/src/routes/zeitstrahl/+page.svelte b/frontend/src/routes/zeitstrahl/+page.svelte
index b00a8160..c84147a1 100644
--- a/frontend/src/routes/zeitstrahl/+page.svelte
+++ b/frontend/src/routes/zeitstrahl/+page.svelte
@@ -1,9 +1,28 @@
@@ -11,6 +30,13 @@ let { data }: { data: PageData } = $props();
-
{m.timeline_heading()}
-
+
+
+
{m.timeline_heading()}
+ {#if hasContent}
+
{metaLine}
+ {/if}
+
+
diff --git a/frontend/src/routes/zeitstrahl/page.svelte.spec.ts b/frontend/src/routes/zeitstrahl/page.svelte.spec.ts
new file mode 100644
index 00000000..50782a41
--- /dev/null
+++ b/frontend/src/routes/zeitstrahl/page.svelte.spec.ts
@@ -0,0 +1,66 @@
+import { describe, it, expect, afterEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import * as m from '$lib/paraglide/messages.js';
+import Page from './+page.svelte';
+import { makeEntry, makeYear, makeTimelineDTO } from '$lib/timeline/test-factories';
+
+afterEach(() => cleanup());
+
+const event = (title: string) =>
+ makeEntry({
+ kind: 'EVENT',
+ derived: true,
+ derivedType: 'BIRTH',
+ title,
+ senderName: '',
+ receiverName: '',
+ documentId: undefined
+ });
+
+describe('/zeitstrahl page', () => {
+ it('wraps the timeline in a bordered, rounded canvas frame (REQ-001)', () => {
+ render(Page, {
+ data: { timeline: makeTimelineDTO({ years: [makeYear(1909, [makeEntry()])] }) }
+ });
+ const canvas = document.querySelector('[data-testid="timeline-canvas"]');
+ expect(canvas).not.toBeNull();
+ expect(canvas?.classList.contains('bg-canvas')).toBe(true);
+ expect(canvas?.classList.contains('border')).toBe(true);
+ // the timeline renders inside the frame
+ expect(canvas?.querySelector('ol')).not.toBeNull();
+ });
+
+ it('renders the meta sub-line with range, counts, and grouping (REQ-002)', () => {
+ const dto = makeTimelineDTO({
+ years: [
+ makeYear(1909, [
+ makeEntry({ documentId: 'a' }),
+ makeEntry({ documentId: 'b' }),
+ event('Geburt')
+ ]),
+ makeYear(1924, [makeEntry({ documentId: 'c' }), event('Tod')])
+ ]
+ });
+ render(Page, { data: { timeline: dto } });
+ const sub = document.querySelector('[data-testid="timeline-meta"]');
+ expect(sub?.textContent).toContain('1909–1924');
+ expect(sub?.textContent).toContain(m.timeline_letters_count({ count: 3 }));
+ expect(sub?.textContent).toContain(m.timeline_events_count({ count: 2 }));
+ expect(sub?.textContent).toContain(m.timeline_grouping_date());
+ });
+
+ it('omits the range segment when there are no year bands (REQ-002)', () => {
+ render(Page, {
+ data: { timeline: makeTimelineDTO({ undated: [makeEntry({ documentId: 'u1' })] }) }
+ });
+ const sub = document.querySelector('[data-testid="timeline-meta"]');
+ expect(sub).not.toBeNull();
+ expect(sub?.textContent).not.toContain('–');
+ expect(sub?.textContent).toContain(m.timeline_letters_count({ count: 1 }));
+ });
+
+ it('omits the entire sub-line for an empty timeline (REQ-002)', () => {
+ render(Page, { data: { timeline: makeTimelineDTO() } });
+ expect(document.querySelector('[data-testid="timeline-meta"]')).toBeNull();
+ });
+});