diff --git a/frontend/src/lib/timeline/LetterCard.svelte b/frontend/src/lib/timeline/LetterCard.svelte
index 4f44a6b4..593156fc 100644
--- a/frontend/src/lib/timeline/LetterCard.svelte
+++ b/frontend/src/lib/timeline/LetterCard.svelte
@@ -2,6 +2,7 @@
import * as m from '$lib/paraglide/messages.js';
import { timelineDateLabel } from './dateLabel';
import GlyphLabel from './GlyphLabel.svelte';
+import TagChip from './TagChip.svelte';
import type { components } from '$lib/generated/api';
type TimelineEntryDTO = components['schemas']['TimelineEntryDTO'];
@@ -46,4 +47,9 @@ const receiver = $derived(
· {dateLabel}
{/if}
+ {#if entry.rootTagName}
+
+
+ {/if}
diff --git a/frontend/src/lib/timeline/LetterCard.svelte.spec.ts b/frontend/src/lib/timeline/LetterCard.svelte.spec.ts
index f9c928bc..b60c0f5f 100644
--- a/frontend/src/lib/timeline/LetterCard.svelte.spec.ts
+++ b/frontend/src/lib/timeline/LetterCard.svelte.spec.ts
@@ -1,7 +1,9 @@
import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
+import { tick } from 'svelte';
import * as m from '$lib/paraglide/messages.js';
import LetterCard from './LetterCard.svelte';
+import YearLetterStrip from './YearLetterStrip.svelte';
import { timelineDateLabel } from './dateLabel';
import { makeEntry } from './test-factories';
@@ -86,4 +88,42 @@ describe('LetterCard', () => {
expect(document.body.textContent).toContain(evil);
expect(document.querySelector('a script')).toBeNull();
});
+
+ it('renders one root-tag chip beneath the meta line when rootTagName is present (REQ-008)', () => {
+ render(LetterCard, { entry: makeEntry({ rootTagName: 'Familie', rootTagColor: 'sage' }) });
+ const chips = document.querySelectorAll('[data-testid="tag-chip"]');
+ expect(chips).toHaveLength(1);
+ expect(chips[0].textContent).toContain('Familie');
+ });
+
+ it('renders no chip when the letter has no root tag (REQ-005/006)', () => {
+ render(LetterCard, { entry: makeEntry({ rootTagName: undefined, rootTagColor: undefined }) });
+ expect(document.querySelector('[data-testid="tag-chip"]')).toBeNull();
+ });
+
+ it('keeps a long tag name from overflowing the card at 320px, full name in the title (REQ-008a)', () => {
+ document.body.style.width = '320px';
+ render(LetterCard, {
+ entry: makeEntry({
+ rootTagName: 'Briefe von der Front und aus der Heimat',
+ rootTagColor: 'sienna'
+ })
+ });
+ const link = document.querySelector('a') as HTMLAnchorElement;
+ expect(link.scrollWidth).toBeLessThanOrEqual(link.clientWidth);
+ const chip = document.querySelector('[data-testid="tag-chip"]') as HTMLElement;
+ expect(chip.getAttribute('title')).toBe('Briefe von der Front und aus der Heimat');
+ document.body.style.width = '';
+ });
+
+ it('renders the chip inside an expanded YearLetterStrip too (REQ-012)', async () => {
+ render(YearLetterStrip, {
+ letters: [makeEntry({ rootTagName: 'Familie', rootTagColor: 'sage', documentId: 'doc-1' })],
+ year: 1909
+ });
+ (document.querySelector('[data-testid="strip-expand"]') as HTMLButtonElement).click();
+ await tick();
+ const chip = document.querySelector('[data-testid="tag-chip"]');
+ expect(chip?.textContent).toContain('Familie');
+ });
});